Cómo transferir los cálculos de cualquier indicador al código de un asesor experto

28 junio 2018, 15:15
Dmitriy Gizlyk
0
7 662

Contenido

Introducción

Cuando un programador crea un asesor que recibe señales de los indicadores, siempre se enfrenta a la pregunta: ¿debo utilizar una referencia al indicador o transferir el código del indicador al asesor? Los motivos para ello pueden ser diferentes: el deseo de mantener los indicadores y la estrategia utilizados en secreto, la necesidad de distribuir el asesor en un solo archivo, el deseo de reducir el número de operaciones realizadas cuando no se utilizan todas las señales/búferes de indicador, etcétera. Por supuesto, no somos los primeros ni los últimos en hacernos esta pregunta. Nikolay Kositsin ya analizó un tema semejante para MetaTrader 4. Vamos a ver cómo podemos hacerlo en la plataforma MetaTrader 5.

1 Principios de transferencia del código

Antes de ponernos a trabajar, vamos a analizar las diferencias entre el funcionamiento de los indicadores y los asesores. Veamos una plantilla de indicador vacía.

//+------------------------------------------------------------------+
//|                                                        Blanc.mq5 |
//|                                             Copyright 2018, DNG® |
//|                                 http://www.mql5.com/ru/users/dng |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, DNG®"
#property link      "http://www.mql5.com/ru/users/dng"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
//--- plot Buffer
#property indicator_label1  "Buffer"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- indicator buffers
double         BufferBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,BufferBuffer,INDICATOR_DATA);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Al comienzo del código del indicador, se declaran las matrices de búfer para intercambiar datos con otros programas. Estas matrices son series temporales, y sus elementos tienen una vinculación con las barras de precios. Esta conexión es compatible directamente con el terminal. El indicador guarda los resultados de los cálculos en estas matrices, sin ocuparse de cambiar su tamaño, ni de transferir los datos cuando aparece una nueva vela. En el asesor no existen estas matrices, por lo que al trasladar el código del indicador al asesor, tendrá que crearlas. Además de los cálculos propiamente dichos, también necesitamos organizar la vinculación entre los elementos de la matriz y las barras en el gráfico del instrumento. La otra cara de la moneda es que dentro del asesor existe la posibilidad de no hacer cálculos con toda la historia existente (lo que sucede en el indicador). Basta con recalcular la profundidad de los datos utilizados.

Por lo tanto, en el asesor se deben crear búferes de indicador. En esta caso, además, debemos recordar que el indicador puede tener no solo búferes para mostrar información en el gráfico, sino también búferes auxiliares para los cálculos intermedios. También tenemos que crearlos. En lo que respecta a los búferes del color de dibujado, podemos ignorarlos si en la estrategia del asesor no se contempla un cambio de color en las líneas de indicador.

Otra diferencia en la arquitectura de indicadores y asesores es la función de procesamiento de ticks. A diferencia de MetaTrader 4, en MetaTrader 5 los procesadores de los ticks entrantes para indicadores y asesores están separados. Al llegar un nuevo tick, en el indicador se llama la función OnCalculate. En los parámetros, esta obtiene el número total de barras en el gráfico, el número de barras en la llamada anterior y las series temporales necesarias para calcular el indicador. En el propio asesor los nuevos ticks se procesan en la función OnTick, que no dispone de parámetros. Por ello, tendremos que crear de forma independiente el acceso a las series temporales y garantizar el monitoreo de los cambios en el gráfico.

2. Creamos la clase para el cálculo del indicador

Con frecuencia, en las estrategias de los asesores, se utiliza un indicador con diferentes parámetros, por lo que, a nuestro juicio, tiene sentido aprovechar las capacidades de la POO y configurar nuestro indicador en la clase CIndicator.

Vamos a resumir. Esto es lo que tenemos que hacer para transferir los cálculos del indicador al asesor.

  1. Organizar el trabajo de los búferes de indicador. Para ello, crearemos la clase CArrayBuffer, y dentro de ella, los métodos de almacenamiento de datos y un acceso cómodo a los mismos. Más tarde crearemos una matriz de ese tipo de clases según el número de búferes en el indicador.
  2. Los cálculos del indicador de la función OnCalculate los trasladaremos a la función Calculate de nuestra clase.
  3. El indicador accede a las series temporales a partir de los parámetros de la función OnCalculate. Las funciones del asesor no disponen de esta posibilidad. Por eso, vamos a organizar la carga de las series temporales necesarias en la función LoadHistory.
  4. Para unificar el acceso a los datos recalculados del indicador, vamos a crear en la clase CIndicator la función CopyBuffer con los parámetros necesarios. 

Todo el trabajo sucesivo se puede resumir en el siguiente esquema.


A conitnuación, al hablar del indicador, tendremos en cuenta la copia del indicador creada en el código del asesor.

2.1. Creamos el búfer de indicador

Para crear los búferes de indicador, usaremos la clase CArrayDouble. Sobre su base crearemos la nueva clase CArrayBuffer.

class CArrayBuffer   :  public CArrayDouble
  {
public:
                     CArrayBuffer(void);
                    ~CArrayBuffer(void);
//---
   int               CopyBuffer(const int start, const int count, double &double_array[]);
   int               Initilize(void);
   virtual bool      Shift(const int shift);
  };

Crearemos el método CopyBuffer para obtener los datos en una forma que recuerde la referencia estándar al indicador. Asimismo, añadiremos dos métodos auxiliares: Initilize — para limpiar los datos del búfer, y Shift — para que los datos se desplacen dentro del búfer al aparecer una nueva vela. Podrá familiarizarse con el código de las funciones en los anexos.

2.2. Clase padre para los indicadores futuros

El siguiente paso consiste en crear el "esqueleto" del indicador en la clase básica CIndicator.

class CIndicator
  {
private:
//---
   datetime             m_last_load;
public:
                        CIndicator(void);
                       ~CIndicator(void);
   virtual bool         Create(const string symbol=NULL, const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
//--- Set indicator's main settings
   virtual bool         SetBufferSize(const int bars);
//--- Get indicator's data
   virtual int          CopyBuffer(const uint buffer_num,const uint start, const uint count, double &double_array[]);
   virtual double       GetData(const uint buffer_num,const uint shift);

protected:
   double               m_source_data[];
   CArrayBuffer         ar_IndBuffers[];
   int                  m_buffers;
   int                  m_history_len;
   int                  m_data_len;
//---
   string               m_Symbol;
   ENUM_TIMEFRAMES      m_Timeframe;
   ENUM_APPLIED_PRICE   m_Price;      
//--- Set indicator's main settings
   virtual bool         SetHistoryLen(const int bars=-1);
//---
   virtual bool         LoadHistory(void);
   virtual bool         Calculate()                         {  return true;   }
  };

En esta clase hay 6 métodos públicos:

  • un constructor,
  • un destructor,
  • un método de inicialización de clase,
  • un método para indicar el tamaño del búfer de indicador
  • dos métodos para acceder a los datos del indicador: uno para cargar los datos por paquetes, y otro para acceder a la dirección de un elemento específico.

La parte principal de los miembros de la clase se declara en la zona protected. Aquí declaramos:

  • la matriz para los datos fuente para el cálculo (m_source_data);
  • la matriz para los búferes de indicador (ar_IndBuffers);
  • las variables para guardar el número de búferes de indicador (m_buffers), la profundidad de la historia de datos necesaria (m_history_len), la profundidad de la historia de valores del indicador (m_data_len);
  • el instrumento (m_Symbol) y el marco temporal (m_Timeframe) utilizados;
  • el tipo de precio para el cálculo del indicador (m_Price);
  • los métodos para la configuración: la profundidad de los datos fuente (SetHistoryLen); la carga de los datos históricos de las series temporales (LoadHistory); el recálculo del indicador (Calculate). 

Todos los métodos son creados de forma virtual para que luego puedan ajustarse a las necesidades de un indicador específico. En el constructor de la clase inicializamos las variables y liberamos las matrices.

CIndicator::CIndicator()   :  m_buffers(0),
                              m_Symbol(_Symbol),
                              m_Timeframe(PERIOD_CURRENT),
                              m_Price(PRICE_CLOSE),
                              m_last_load(0)
  {
   m_data_len=m_history_len  =  Bars(m_Symbol,m_Timeframe)-1;
   ArrayFree(ar_IndBuffers);
   ArrayFree(m_source_data);
  }

En la función de inicialización de la clase, primero comprobamos si se puede usar el símbolo especificado. Para ello, comprobaremos si está activo en la Observación del Mercado, si no, intentamos elegirlo. Si no es posible usar el símbolo, la función retorna el valor false. Si la comprobación tiene éxito, guardamos el símbolo, el marco temporal y el precio utilizado para el cálculo en las variables correspondientes.

bool CIndicator::Create(const string symbol=NULL,const ENUM_TIMEFRAMES timeframe=0,const ENUM_APPLIED_PRICE price=1)
  {
   m_Symbol=(symbol==NULL ? _Symbol : symbol);
   if(!SymbolInfoInteger(m_Symbol,SYMBOL_SELECT))
      if(!SymbolSelect(m_Symbol,true))
         return false;
//---
   m_Timeframe=timeframe;
   m_Price=price;
//---
   return true;
  }

El método que establece el tamaño del búfer de indicador solo tiene un parámetro: el tamaño en sí mismo. En este caso, además, si queremos usar todas la historia disponible, bastará con transmitir a la función una cifra igual o menor a "0". En la propia función, primero guardamos el valor del parámetro transmitido a la variable correspondiente. A continuación, comprobamos que los datos históricos de las series temporales sean suficientes para obtener la historia establecida del indicador. Si los valores iniciales son insuficientes, el tamaño de los datos descargados aumentará. Al final de la función, limpiamos y cambiamos el tamaño de todos los búferes de indicador.

bool CIndicator::SetBufferSize(const int bars)
  {
   if(bars>0)
      m_data_len  =  bars;
   else
      m_data_len  =  Bars(m_Symbol,m_Timeframe);
//---
   if(m_data_len<=0)
     {
      for(int i=0;i<m_buffers;i++)
         ar_IndBuffers[i].Shutdown();
      return false;
     }
//---
   if(m_history_len<m_data_len)
      if(!SetHistoryLen(m_data_len))
         return false;
//---
   for(int i=0;i<m_buffers;i++)
     {
      ar_IndBuffers[i].Shutdown();
      if(!ar_IndBuffers[i].Resize(m_data_len))
         return false;
     }
//---
   return true;
  }

Para obtener los datos históricos de las series temporales, se usa la función LoadHistory. No tiene parámetros, y obtiene los valores iniciales de los datos almacenados en las funciones anteriores.

Con frecuencia, durante la formación de la vela, también cambia la indicación actual del indicador, y esto puede llevar a la aparición de señales falsas. Por esta razón, muchas estrategias de indicadores usan los datos de las velas cerradas. Partiendo de esta lógica, para las necesidades del asesor será suficiente cargar una vez los datos históricos cuando se forme una nueva vela. Por ello, al comienzo de la función, comprobamos la apertura de una nueva barra. Si la nueva barra no se abre y los datos ya están cargados, salimos de la función. Si necesitamos cargar los datos, pasamos al siguiente bloque de funciones. Si para calcular el indicador basta una serie temporal, cargamos los datos necesarios en nuestra matriz de datos fuente. Cuando el indicador utiliza el precio mediano, típico o promedio ponderado, primero cargamos los datos históricos en la matriz de la estructura MqlRates, y después organizamos en el ciclo el cálculo del precio necesario. Los resultados del cálculo se guardan en la matriz de datos fuente para su uso posterior.

bool CIndicator::LoadHistory(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(m_Symbol,m_Timeframe,SERIES_LASTBAR_DATE);
   if(m_last_load>=cur_date && ArraySize(m_source_data)>=m_history_len)
      return true;
//---
   MqlRates rates[];
   int total=0,i;
   switch(m_Price)
     {
      case PRICE_CLOSE:
        total=CopyClose(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
        break;
      case PRICE_OPEN:
        total=CopyOpen(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_HIGH:
        total=CopyHigh(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_LOW:
        total=CopyLow(m_Symbol,m_Timeframe,1,m_history_len,m_source_data);
      case PRICE_MEDIAN:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low)/2;
        break;
      case PRICE_TYPICAL:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low+rates[i].close)/3;
        break;
      case PRICE_WEIGHTED:
        total=CopyRates(m_Symbol,m_Timeframe,1,m_history_len,rates);
        if(total!=ArraySize(m_source_data))
           total=ArrayResize(m_source_data,total);
        for(i=0;i<total;i++)
           m_source_data[i]=(rates[i].high+rates[i].low+2*rates[i].close)/4;
        break;
     }
//---
   if(total<=0)
      return false;
//---
   m_last_load=cur_date;
   return (total>0);
  }

Si en el proceso de ejecución de la función los datos no han sido cargados, la función retornará false.

El método de obtención de los datos de los indicadores lo implementaremos por analogía con el acceso estándar a los búferes de indicador. Para ello, crearemos la función CopyBuffer, en cuyos parámetros transmitireremos el número de búfer, la posición del comienzo del copiado de datos, el número de elementos necesarios y, propiamente, la matriz para obtener los datos. Después de su ejecución, la función retornará el número de elementos copiados.

int CIndicator::CopyBuffer(const uint buffer_num,const uint start,const uint count,double &double_array[])
  {
   if(!Calculate())
      return -1;
//---
   if((int)buffer_num>=m_buffers)
     {
      ArrayFree(double_array);
      return -1;
     }
//---
   return ar_IndBuffers[buffer_num].CopyBuffer(start,count,double_array);
  }

Para que el usuario siempre obtenga datos actuales, al comienzo de la función llamamos a la función de recálculo del indicador (en esta clase solo declaramos la función virtual, y haremos el cálculo directamente en la clase final del indicador). Después de recalcular los valores del indicador, comprobamos que tenga un búfer indicado. Si el número del búfer es incorrecto, borramos la matriz de destino y salimos de la función con el resultado "-1". Si el número de búfer se comprueba con éxito, llamamos al método CopyBuffer de la matriz de búfer correspondiente.

La función de acceso a los datos de direcciones está construida de manera similar.

Podrá encontrar en el archivo adjunto el código completo de la clase y todas sus funciones.

2.3. Clase de indicador de la media móvil

Para mostrar la tecnología hemos elegido el indicador de media móvil (МА). La elección no es casual. Este indicador de análisis técnico no solo es usado por los tráders en su forma clásica, también se utiliza ampliamente para construir otros indicadores. Se trata tanto de MACD, como Alligator, como muchos otros. Además, en el suministro del "paquete" hay un ejemplo de МА, a partir de la cual podemos obtener los datos a través de la función iCustom, para comparar la velocidad de acceso al indicador con la velocidad de los datos en el asesor.

Calcular la МА en la clase CMA. Nuestra clase obtendrá 4 métodos públicos: un constructor, un destructor, un método de inicialización (Create) y un método para establecer la profundidad de los datos históricos del indicador (que reescribiremos). Nuestra clase heredará desde la clase padre los métodos de acceso a los datos del indicador.

class CMA : public CIndicator
  {
private:
   int               m_Period;
   int               m_Shift;
   ENUM_MA_METHOD    m_Method;
   datetime          m_last_calculate;
   
public:
                     CMA();
                    ~CMA();
   bool              Create(const string symbol, const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE price=PRICE_CLOSE);
   virtual bool      SetBufferSize(const int bars);
   
protected:
   virtual bool      Calculate();
   virtual double    CalculateSMA(const int shift);
   virtual double    CalculateEMA(const int shift);
   virtual double    CalculateLWMA(const int shift);
   virtual double    CalculateSMMA(const int shift);
  };

Como puede notar en el encabezado de clase más arriba, en esta etapa aparecen elementos para el cálculo directo del indicador. Se trata de variables privadas para almacenar el periodo, el desplazamiento y el método de cálculo del indicador. En el bloque protected, reescribiremos la función virtual de cálculo del indicador Calculate. Dependiendo del método indicado para el cálculo del indicador, esta llamará a las subfunciones CalculateSMA, CalculateEMA, CalculateLWMA o CalculateSMMA.

En el constructor de la clase, inicializamos las variables, especificamos el número de búferes del indicador y creamos un búfer de indicador.

CMA::CMA()  :  m_Period(25),
               m_Shift(0),
               m_Method(MODE_SMA)
  {
   m_buffers=1;
   ArrayResize(ar_IndBuffers,1);
  }

En los parámetros de la función de inicialización de clase, indicaremos el símbolo, el marco temporal y los parámetros necesarios para calcular el indicador. En la propia función, primero llamaremos a la función de inicialización de la clase padre. A continuación, comprobaremos la validez de este periodo de promediación (debe ser positivo). Después de ello, guardamos los parámetros del indicador en las variables de clase correspondientes e indicamos la profundidad de la historia para el búfer de indicador y los datos cargados de las series temporales. Si surge un error, la función retornará false. Después de inicializar con éxito, se retornará true.

bool CMA::Create(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int ma_shift,const ENUM_MA_METHOD ma_method,const ENUM_APPLIED_PRICE price=1)
  {
   if(!CIndicator::Create(symbol,timeframe,price))
      return false;
//---
   if(ma_period<=0)
      return false;
//---
   m_Period=ma_period;
   m_Shift=ma_shift;
   m_Method=ma_method;
//---
   if(!SetBufferSize(ma_period))
      return false;
   if(!SetHistoryLen(2*ma_period+(m_Shift>0 ? m_Shift : 0)))
      return false;
//---
   return true;
  }

La función Calculate calculará directamente el indicador. Anteriormente, al crear la clase padre, teníamos que decidir que vamos a cargar los datos históricos de las series temporales al abrir una nueva vela. Por consiguiente, vamos recalcular los datos del indicador con la misma frecuencia. Para ello, al inicio de la función, comprobamos la apertura de una nueva vela. Si en la barra actual ya se ha realizado el cálculo, salimos de la función con el resultado true.

A continuación, si se ha abierto una nueva barra, llamamos a la función de carga de los datos de las series temporales. Si los datos históricos se cargan con éxito, comprobamos el número de velas generadas después del último recálculo del indicador. Si el número de velas nuevas es superior al tamaño de nuestro búfer de indicador, entonces lo inicializamos de nuevo. Si hay menos velas nuevas, cambiamos los datos dentro de nuestro búfer a la cantidad de barras que ha aparecido. Después, recalculamos solo los elementos nuevos.

Ahora organizamos un ciclo para volver a calcular los nuevos elementos del búfer de indicador. Preste atención: si el elemento recalculado excede el tamaño del búfer de indicador actual (esto es posible en el primer inicio del cálculo o en el cálculo después de cortarse la comunicación, cuando el número de velas nuevas supera al tamaño del búfer), los datos se añadirán al búfer con el método Add. Si el elemento recalculado entra en el tamaño del búfer existente, el valor del elemento se actualizará mediante el método de Update. El cálculo directo de los valores del indicador se realiza en las subfunciones correspondientes al método de promediación. La lógica de cálculo se toma del indicador Custom Moving Average.mq5 del paquete estándar de MetaTrader 5.

Después de convertir con éxito el búfer de indicador, guardamos la fecha y la hora de la última conversión y salimos de la función con el resultado true.

bool CMA::Calculate(void)
  {
   datetime cur_date=(datetime)SeriesInfoInteger(m_Symbol,m_Timeframe,SERIES_LASTBAR_DATE);
   if(m_last_calculate==cur_date && ArraySize(m_source_data)==m_history_len)
      return true;
//---
   if(!LoadHistory())
      return false;
//---
   int shift=Bars(m_Symbol,m_Timeframe,m_last_calculate,cur_date)-1;
   if(shift>m_data_len)
     {
      ar_IndBuffers[0].Initilize();
      shift=m_data_len;
     }
   else
      ar_IndBuffers[0].Shift(shift);
//---
   for(int i=(m_data_len-shift);i<m_data_len;i++)
     {
      int data_total=ar_IndBuffers[0].Total();
      switch(m_Method)
        {
         case MODE_SMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateSMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateSMA(i+m_Shift));
           break;
         case MODE_EMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateEMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateEMA(i+m_Shift));
           break;
         case MODE_SMMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateSMMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateSMMA(i+m_Shift));
           break;
         case MODE_LWMA:
           if(i>=data_total)
              ar_IndBuffers[0].Add(CalculateLWMA(i+m_Shift));
           else
              ar_IndBuffers[0].Update(i,CalculateLWMA(i+m_Shift));
           break;
        }
     }
//---
   m_last_calculate=cur_date;
   m_data_len=ar_IndBuffers[0].Total();
//---
   return true;
  }

En esta misma clase, reescribiremos la función virtual de la clase padre, que establece el tamaño necesario del búfer de indicador. Esto resulta necesario debido a la comprobación de la profundidad del búfer de indicador y la profundidad de los datos históricos de las series temporales. En la clase padre hemos indicado que el número de elementos en la serie temporal no deberá ser menor al número de elementos en el búfer de indicador. Y para calcular la МА, el número de elementos de la serie temporal deberá ser superior al tamaño del búfer de indicador como mínimo en una cantidad igual al periodo de promediación.

3. Ejemplo de adición de una clase de indicador a un asesor

Cuando planeamos escribir este artículo, una de nuestras tareas era comparar la velocidad de procesamiento dentro del asesor y la obtención de datos del indicador. Por ello, para mostrar el funcionamiento de la clase, hemos decidido no crear un robot comercial completo. En cambio, le ofrecemos una plantilla de asesor en la que usted podrá terminar de escribir su propia lógica de procesamiento para las señales del indicador.

Vamos a crear un nuevo archivo del asesor Test_Class.mq5. Sus parámetros de entrada serán similares a los parámetros del indicador utilizado.

input int                  MA_Period   =  25;
input int                  MA_Shift    =  0;
input ENUM_MA_METHOD       MA_Method   =  MODE_SMA;
input ENUM_APPLIED_PRICE   MA_Price    =  PRICE_CLOSE;

A nivel general, declaramos un ejemplar de nuestra clase de indicador y la matriz para obtener los datos del indicador.

CMA   *MA;
double c_data[];

En la función OnInit deberemos inicializar un ejemplar de la clase de indicador y transmitir al mismo los datos fuente.

int OnInit()
  {
//---
   MA=new CMA;
   if(CheckPointer(MA)==POINTER_INVALID)
      return INIT_FAILED;
//---
   if(!MA.Create(_Symbol,PERIOD_CURRENT,MA_Period,MA_Shift,MA_Method,MA_Price))
      return INIT_FAILED;
   MA.SetBufferSize(3);
//---
   return(INIT_SUCCEEDED);
  }

Cuando finalice el funcionamiento del asesor, deberemos limpiar la memoria y eliminar el ejemplar de nuestra clase, lo cual haremos en la función OnDeinit.

void OnDeinit(const int reason)
  {
//---
   if(CheckPointer(MA)!=POINTER_INVALID)
      delete MA;
  }

Ahora la clase está lista para funcionar. Solo queda añadir la obtención de los datos del indicador en la función OnTick. Al inicio de la función comprobamos la apertura de una nueva barra, después llamamos al método CopyBuffer de nuestra clase. Luego seguirá nuestro propio código de procesamiento de señales y ejecución de transacciones comerciales.

void OnTick()
  {
//---
   static datetime last_bar=0;
   datetime cur_date=(datetime)SeriesInfoInteger(_Symbol,PERIOD_CURRENT,SERIES_LASTBAR_DATE);
   if(last_bar==cur_date)
      return;
   last_bar=cur_date;
//---
   if(!MA.CopyBuffer(MAIN_LINE,0,3,c_data))
      return;

//---
//     Aquí puede añadir su propio código de procesamiento de señales y transacciones comerciales
//---
   return;
  }

El código completo de todos los programas y clases está disponible en el archivo adjunto.

4. "Coste" del uso del indicador transferido

Y otra cuestión importante: ¿cómo afectará el traslado del código del indicador al funcionamiento del asesor? Para responder a esta pregunta, realizaremos varios experimentos.

4.1. Experimento 1

Ya hemos dicho que no hemos elegido la МА por casualidad. Ahora podremos comprobar la velocidad de obtención de los mismos datos con tres métodos distintos:

  • a través de la función del indicador integrado en el terminal (iMA);
  • llamando a un indicador personalizado similar (iCustom);
  • directamente con el cálculo dentro del asesor.

Lo primero que nos viene a la cabeza es usar la función de perfilado del editor MetaEditor. Para ello, crearemos un asesor no comercial, que recibirá simultáneamente los datos de las tres fuentes. No es necesario dar aquí una descripción completa del funcionamiento de este asesor, podrá familiarizarse con su código en los anexos. Solo diremos que, para que el experimento sea más puro, solo se ha accedido a las tres fuentes de datos al abrirse una nueva vela.

El perfilado se ha realizado en el simulador de estrategias durante 15 meses en el marco temporal М15. Tras efectuar el experimento, hemos obtenido la siguiente información.

Función Tiempo promedio de ejecución, microsegundos Parte del tiempo total
OnTick
99.14%
Comprobación de la apertura de una nueva barra 0.528 67,23%
Cálculo interno
21.524 2.36%
      incluido CopyClose 1.729  0.19%
 iMA  2.231  0.24%
 iCustom  0.748  0.08%
 OnInit  241439  0.86%
 Obtener el manejador iCustom  235676  0.84%

Lo primero que "salta a la vista" es la gran cantidad de tiempo necesario para obtener el manejador del indicador a través de la función iCustom. Supera en diez veces el tiempo necesario para inicializar la clase de indicador y obtener el manejador a través de la función iMA. Al mismo tiempo, la obtención de datos desde el indicador inicializado por la función iCustom, tiene lugar 3 veces más rápido que la obtención desde el indicador iMA, y 30 veces más rápido que el cálculo del valor del indicador en la clase.


Vamos a analizar con más detalle el tiempo de ejecución de las diferentes funciones de nuestra clase de indicador. Fíjese en que el propio momento de obtención de los datos históricos con la función CopyClose es comparable con el momento de obtención de los datos de los indicadores. ¿Es acaso posible que el indicador casi no invierta tiempo en el cálculo? En realidad, todo es un poco diferente. En la arquitectura de MetaTrader 5 se ha organizado un acceso asincrónico a los valores de los indicadores. En otras palabras, al obtener el manejador del indicador, este se adjunta al programa. A continuación, este indicador hace sus cálculos fuera del flujo del asesor. Estos interactúan solo en la etapa de transmisión de datos, de forma análoga a la obtención de los datos de las series temporales. Por eso, el tiempo de ejecución de estas operaciones es comparable.

Vamos a resumir lo mencionado anteriormente en este experimento: hemos mostrado el error al usar la función de perfilado del MetaEditor para valorar el tiempo dedicado al cálculo de los indicadores utilizados en los asesores.

4.2. Experimento 2

Crearemos 4 asesores independientes.

  1. El de referencia será un asesor vacío que no realiza ninguna función. Servirá para valorar el tiempo que invierte el terminal en iterar la historia de las cotizaciones.
  2. Asesor que obtiene los datos mediante el cálculo de valores en la clase de indicador.
  3. Asesor que obtiene los datos desde el indicador iMA.
  4. Asesor que obtiene los datos desde el indicador personalizado.

Después de eso, iniciamos su optimización en 11 pasadas en el simulador de estrategias y comparamos el tiempo promedio de una pasada.

Experimento 2Experimento 2

Los resultados de la simulación han demostrado un ahorro significativo de tiempo al usar los cálculos dentro del asesor. En lo que respecta al consumo de tiempo, la más costosa ha sido la obtención de datos de un indicador personalizado.


Preste atención: en el experimento se ha calculado una MA al precio de cierre. Los cálculos de este indicador son bastante sencillos. Así que surge la siguiente pregunta: ¿cómo cambiará la situación si los cálculos se vuelven más complicados? Lo descubriremos después de otro experimento.

4.3. Experimento 3

Este experimento repite el anterior, pero con el objetivo de aumentar la carga de los cálculos, los indicadores se han calculado para la promediación linealmente ponderada del precio promedio ponderado.

Experimento 3Experimento 3

Podemos ver que el gasto de tiempo ha aumentando con todos los métodos de obtención de datos. En este caso, se observa un aumento proporcional en el tiempo de una pasada, que en general ha confirmado los resultados del experimento anterior.


Conclusión

En el artículo se ha mostrado la tecnología de transferencia de los cálculos del indicador al asesor. La aplicación de la POO permite hacer el acceso a los datos finales del indicador lo más cercano posible a la obtención estándar de información con búferes de indicador. Esto requiere una intervención mínima en el código fuente del asesor al rehacer el mismo.

Según los resultados de los experimentos realizados, este enfoque también puede ahorrar tiempo durante la simulación y la optimización de los asesores. Pero cuando el asesor funciona en tiempo real, esta ventaja puede ser nivelada por la arquitectura de subprocesos múltiples de MetaTrader 5.

Programas usados en el artículo:

#
 Nombre
Tipo 
Descripción 
1 Indicarot.mqh  Biblioteca de la clase  Clase básica para trasladar indicadores.
2 MA.mqh  Biblioteca de la clase  Clase para recalcular el indicador MA dentro del asesor
3 Test.mq5  Experto  Experto para ejecutar el experimento 1
4 Test_Class.mq5  Experto  Experto para calcular el indicador dentro del asesor (experimentos 2 y 3)
5 Test_iMA.mq5  Experto  Experto con obtención de datos del indicador a través de iMA
6 Test_iCustom.mq5  Experto  Experto con obtención de datos del indicador a través de iCustom


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

Archivos adjuntos |
MQL5.zip (164.68 KB)
Mejorando el trabajo con Paneles: cómo añadir transparencia, cambiar el color del fondo y heredar a partir de CAppDialog/CWndClient Mejorando el trabajo con Paneles: cómo añadir transparencia, cambiar el color del fondo y heredar a partir de CAppDialog/CWndClient

Vamos a continuar estudiando el funcionamiento de CAppDialog. Ahora vamos a aprender cómo establecer el color de fondo, el borde y el encabezado para un panel gráfico. Veremos paso a paso cómo agregar transparencia a la ventana de la aplicación al desplazar esta por el gráfico. A continuación, analizaremos la creación de descendientes de CAppDialog o CWndClient y veremos nuevos detalles importantes al trabajar con los controles. Finalmente, echaremos un vistazo desde una nueva perspectiva a nuevos proyectos.

Neuroredes profundas (Parte VI). Conjunto de clasificadores de redes neuronales: bagging Neuroredes profundas (Parte VI). Conjunto de clasificadores de redes neuronales: bagging

Vamos a ver los métodos de construcción y entrenamiento de conjuntos de redes neuronales con la estructura bagging. También vamos a definir las peculiaridades de la optimización de los hiperparámetros de los clasificadores de redes neuronales individuales que componen el conjunto. Asimismo, compararemos la calidad de la red neuronal optimizada obtenida en el artículo anterior de la serie, y el conjunto creado de redes neuronales. Para finalizar, analizaremos las diferentes opciones para mejorar aún más la calidad de clasificación del conjunto.

Cómo analizar las transacciones de la Señal elegida en el gráfico Cómo analizar las transacciones de la Señal elegida en el gráfico

El servicio de señales comerciales se desarrolla a pasos agigantados. A la hora de confiar nuestro dinero a un proveedor de señales, querríamos minimizar el riesgo de pérdida del depósito. Pero, ¿cómo aclararse entre semejante cantidad de señales? ¿Cómo encontrar precisamente aquella que nos reportará beneficios? En este artículo vamos a crear un método para analizar visualmente la historia de transacciones de las señales comrciales en el gráfico del instrumento.

Trading social. ¿Es posible mejorar una señal rentable? Trading social. ¿Es posible mejorar una señal rentable?

La mayoría de los suscriptores eligen una señal comercial por la belleza de su curva de balance o según el número de suscriptores. Por eso, muchos proveedores de hoy se preocupan de que sus estadísticas sean bonitas, en lugar de prestar atención a la calidad real de la señal. A menudo juegan con los volúmenes de las transacciones y confieren artificialmente a la curva de balance un aspecto ideal. En este artículo vamos a analizar los criterios de fiabilidad de las señales, así como los métodos que ayudan al proveedor a mejorar la calidad de las mismas. Además, mostraremos el análisis de una señal en concreto, junto con los métodos que podrían ayudar al proveedor a hacerla más rentable y con menos riesgos.