Cómo transferir los cálculos de cualquier indicador al código de un asesor experto
Contenido
- Introducción
- 1 Principios de transferencia del código
- 2. Creamos la clase para el cálculo del indicador
- 3. Ejemplo de adición de una clase de indicador a un asesor
- 4. "Coste" del uso del indicador transferido
- Conclusión
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.
- 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.
- Los cálculos del indicador de la función OnCalculate los trasladaremos a la función Calculate de nuestra clase.
- 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.
- 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.
- 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.
- Asesor que obtiene los datos mediante el cálculo de valores en la clase de indicador.
- Asesor que obtiene los datos desde el indicador iMA.
- 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.
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.
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 Ltd.
Artículo original: https://www.mql5.com/ru/articles/4602
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso