LifeHack para tráders: preparando "comida rápida" a partir de indicadores

8 febrero 2018, 14:33
Vladimir Karputov
0
2 221

Si está prohibido, pero tenemos muchas ganas, entonces podemos hacerlo.  Dicho ruso

Sencillez vs Fiabilidad

En el lejano 2005 se lanzó oficialmente MetaTrader 4, donde el lenguaje MQL4 sustituía al sencillo lenguaje de scripts MQL-II. Resulta curioso recordar el desagrado inicial con el que los tráders recibieron el nuevo lenguaje, semejante al lenguaje C. En los foros se celebraban furibundos debates en los que se acusaba directamente al desarrollador MetaQuotes Software Corp. de haber creado un lenguaje muy complicado y casi imposible de asimilar.

Ahora, 12 años después, los tráders actuales no comprenden las quejas sobre la complejidad de MQL4, pero la historia se repite. Al igual que entonces, algunos tráders se quejan de que MQL5 es complicado en cuanto a su aprendizaje y a la escritura de estrategias, al contrario que MQL4. Por lo tanto, podemos entender que el nivel general de programación de los robots comerciales ha aumentado significativamente en los últimos años, y todo ello gracias a que el desarrollador no ha tenido miedo de evolucionar y proporcionar a los tráders algorítmicos herramientas más potentes del lenguaje C ++. El nuevo MQL5 permite a los programadores comprobar con el máximo de detalle los resultados de todas las operaciones (algo especialmente importante a la hora de procesar transacciones comerciales), usando la memoria operativa escrupulosamente y a demanda. En el antiguo MQL4, antes de su evolución a MQL5, esta clase de posibilidades era mucho menor. Y la propia sintaxis era menos rigurosa.

Seguro que dentro de poco, el debate sobre si el lenguaje MQL5 es más complejo o no también pasará a la historia. Pero, dado que muchos tráders aún piensan: "MQL4, mi querido MQL4", vamos a intentar mostrarles qué aspecto pueden tener sus funciones MQL4 normales, pero implementadas en MQL5.

Si usted va a dar el salto a MQL5 solo ahora, entonces este artículo le vendrá estupendamente: por una parte, el acceso a los datos de los indicadores y a las serie se ha ejecutado en el estilo MQL4, al que usted ya está acostumbrado, y por otro, toda la implementación se ha creado en MQL5 con esta misma sencillez. Todas las funciones son totalmente comprensibles y se adecuan perfectamente a la depuración paso a paso.


1 ¿Podemos trabajar en MQL5 con indicadores en el estilo MQL4?

La principal diferencia a la hora de trabajar con los indicadores consiste en lo siguiente: en MQL4, la línea para acceder a los datos del indicador es, en esencia, un comando de creación del indicador ( iMACD(NULL,0,12,26,9,PRICE_CLOSE ) y, al mismo tiempo, una solicitud de los datos del búfer de indicador necesario ( MODE_MAIN ) y del índice necesario ( 1 ).

//+------------------------------------------------------------------+
//|                                                        iMACd.mq4 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.00"
#property strict
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
  }
//+------------------------------------------------------------------+

Como resultado, tenemos una sola línea, un solo paso.

En MQL5, el análogo de este código contiene varios pasos:

  • la declaración de la variable en la que se guardará el manejador del indicador;
  • la creación y comprobación del manejador del indicador;
  • una función aparte, que muestra el valor del indicador.
//+------------------------------------------------------------------+
//|                                                        iMACD.mq5 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.000"

int    handle_iMACD;                         // variable for storing the handle of the iMACD indicator
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create handle of the indicator iMACD
   handle_iMACD=iMACD(Symbol(),Period(),12,26,9,PRICE_CLOSE);
//--- if the handle is not created 
   if(handle_iMACD==INVALID_HANDLE)
     {
      //--- tell about the failure and output the error code 
      PrintFormat("Failed to create handle of the iMACD indicator for the symbol %s/%s, error code %d",
                  Symbol(),
                  EnumToString(Period()),
                  GetLastError());
      //--- the indicator is stopped early 
      return(INIT_FAILED);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACDGet(MAIN_LINE,1);
  }
//+------------------------------------------------------------------+
//| Get value of buffers for the iMACD                               |
//|  the buffer numbers are the following:                           |
//|   0 - MAIN_LINE, 1 - SIGNAL_LINE                                 |
//+------------------------------------------------------------------+
double iMACDGet(const int buffer,const int index)
  {
   double MACD[1];
//--- reset error code 
   ResetLastError();
//--- fill a part of the iMACDBuffer array with values from the indicator buffer that has 0 index 
   if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0)
     {
      //--- if the copying fails, tell the error code 
      PrintFormat("Failed to copy data from the iMACD indicator, error code %d",GetLastError());
      //--- quit with zero result - it means that the indicator is considered as not calculated 
      return(0.0);
     }
   return(MACD[0]);
  }
//+------------------------------------------------------------------+

Vamos a reescribir este código en el estilo MQL4.

Vamos a escribir la creación del manejador del indicador y la obtención de datos del indicador en una función:

//+------------------------------------------------------------------+
//| iMACD function in MQL4 notation                                  |
//+------------------------------------------------------------------+
double iMACD(
             string              symbol,              // symbol name 
             ENUM_TIMEFRAMES     period,              // period 
             int                 fast_ema_period,     // period for Fast average calculation 
             int                 slow_ema_period,     // period for Slow average calculation 
             int                 signal_period,       // period for their difference averaging 
             ENUM_APPLIED_PRICE  applied_price,       // type of price or handle 
             int                 buffer,              // buffer
             int                 shift                // shift
             )
  {
   double result=NULL;
//---
   int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period,
                    applied_price);
   double val[1];
   int copied=CopyBuffer(handle,buffer,shift,1,val);
   if(copied>0)
      result=val[0];
   return(result);
  }

¡Y ahora, ATENCIÓN! Una vez escrita esta función, crearemos el manejador del indicador EN CADA tick. Sin embargo, este "ingenio" no está recomendado por la documentación. Esto es lo que dice la guía sobre la función para trabajar con indicadores técnicos:

No es posible recurrir a los datos del indicador justo después de crearlo, ya que para el cálculo de los valores del indicador se necesita cierto tiempo. Por eso, lo mejor es crear los manejadores del indicador en OnInit().

Entonces, ¿por qué este código funciona y no consume memoria? La respuesta se encuentra en este apartado, más abajo:

Nota. Recurrir varias veces a la función del indicador con los mismos parámetros dentro de un programa MQL5 no provoca el aumento múltiple del contador de enlaces, el contador aumentará 1 solo una vez. Sin embargo, recomendamos obtener los manejadores en la función OnInit() o bien en el constructor de la clase, usando posteriormente los manejadores obtenidos en las demás funciones. El contador de enlaces disminuirá al desinicializar el programa mql5.

En otras palabras, MQL5 se ha proyectado de forma óptima: él mismo controla la creación de manejadores y no permite crear el mismo indicador con los mismos parámetros. En el caso de intentar crear de forma repetida un manejador-copia del indicador, a usted se le retornará el manejador del indicador creado anteriormente con los parámetros correspondientes. Sin embargo, recomendamos obtener en cualquier caso los manejadores una vez en OnInit(): veremos el motivo un poco más abajo.

Preste atención: no existe comprobación de la corrección del manejador creado.

Ahora el código que recibe los valores del indicador iMACD tendrá el aspecto siguiente:

//+------------------------------------------------------------------+
//|                                           MACD MQL4 style EA.mq5 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.000"

#define MODE_MAIN 0
#define MODE_SIGNAL 1
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
  }
//+------------------------------------------------------------------+
//| iMACD function in MQL4 notation                                  |
//+------------------------------------------------------------------+
double iMACD(
             string              symbol,              // symbol name 
             ENUM_TIMEFRAMES     period,              // period 
             int                 fast_ema_period,     // period for Fast average calculation 
             int                 slow_ema_period,     // period for Slow average calculation 
             int                 signal_period,       // period for their difference averaging 
             ENUM_APPLIED_PRICE  applied_price,       // type of price or handle 
             int                 buffer,              // buffer
             int                 shift                // shift
             )
  {
   double result=NULL;
//---
   int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period,
                    applied_price);
   double val[1];
   int copied=CopyBuffer(handle,buffer,shift,1,val);
   if(copied>0)
      result=val[0];
   return(result);
  }
//+------------------------------------------------------------------+

ATENCIÓN: el deseo de recurrir a los indicadores en el estilo MQL4 nos priva de la opción con comprobación del valor retornado, puesto que todas las funciones en estilo MQL4 retornan SOLO valores del tipo double. Mostraremos una posible solución en el apartado 1.1.

Por el momento, todo tiene un aspecto bastante engorroso, por eso el bloque de "defines" y la función double iMACD() los vamos a sacar al archivo de inclusión aparte "IndicatorsMQL5.mqh", que ubicaremos en la carpeta separada "[data folder]\MQL5\Include\SimpleCall". Ahora el código se hace bastante más corto. Preste atención:  incluimos el archivo "IndicatorsMQL5.mqh". Esto siginifica que los nombres de las líneas de indicador al recurrir a MACD los deberemos transmitir en forma de MQL5 MAIN_LINE, y no en forma de MQL4 MODE_MAIN:

//+------------------------------------------------------------------+
//|                                     MACD MQL4 style EA short.mq5 |
//|                              Copyright © 2018, Vladimir Karputov |
//|                                           http://wmua.ru/slesar/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2018, Vladimir Karputov"
#property link      "http://wmua.ru/slesar/"
#property version   "1.000"
#include <SimpleCall\IndicatorsMQL5.mqh>
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1);
   Comment("MACD, main buffer, index 1: ",DoubleToString(macd_main_1,Digits()+1));
  }
//+------------------------------------------------------------------+

"Comment" lo hemos introducido exclusivamente para la comprobación. Podemos coprobar el funcionamiento en el simulador si iniciamos "MACD MQL4 style EA short.mq5" en el modo visual y colocamos el cursor en la barra con el índice #1:

"MACD MQL4 style EA short.mh5" in tester

Fig. 1 "MACD MQL4 style EA short.mh5" in tester

1.1. Detalles a considerar a la hora de trabajar con "IndicatorsXXXX.mqh"


El procesamiento del error en el valor retornado

Todos los indicadores transmiten sus datos como double. En esto consiste precisamente el problema del envío de mensajes al usuario en el caso de que no se haya conseguido obtener los datos del indicador. Esta situación puede surgir si falla la creación del manejador del indicador (por ejemplo, si se ha indicado un símbolo inexistente) o al darse un error en el copiado al llamar CopyBuffer.

Transmitir simplemente "0.0" en caso de error no es una salida, puesto que para muchos indicadores, "0.0" es un índice totalmente normal (por ejemplo, para MACD). Retornar la constante EMPTY_VALUE (que, por cierto, tiene el valor DBL_MAX) tampoco es una opción, ya que el indicador Fractals rellena los índices del búfer con valores EMPTY_VALUE, lo que conlleva que esto para él no constituye un error.

Queda la opción de tansmitir un "no es un número": NaN. Para ello, a nivel global se declara la variable "NaN", que precisamente se inicializa como "no es un número":

double NaN=double("nan");
//+------------------------------------------------------------------+
//| iAC function in MQL4 notation                                    |
//+------------------------------------------------------------------+
double   iAC(
             string                       symbol,              // symbol name 
             ENUM_TIMEFRAMES              timeframe,           // timeframe 
             int                          shift                // shift
             )
  {
   double result=NaN;
//---
   int handle=iAC(symbol,timeframe);
   if(handle==INVALID_HANDLE)
     {
      Print(__FUNCTION__,": INVALID_HANDLE error=",GetLastError());
      return(result);

     }
   double val[1];
   int copied=CopyBuffer(handle,0,shift,1,val);
   if(copied>0)
      result=val[0];
   else
      Print(__FUNCTION__,": CopyBuffer error=",GetLastError());
   return(result);
  }

La ventaja de este enfoque también reside en que ahora, en caso de error, se retorna NaN, y el resultado de su comparación con cualquier número será false.

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- ejemplo de comparación de NaN
   double NaN=double("nan");
   double a=10.3;
   double b=-5;
   double otherNaN=double("nan");
   Print("NaN>10.3=",NaN>a);
   Print("NaN<-5=",NaN<b);
   Print("(NaN==0)=",NaN==0);
   Print("(NaN==NaN)=",NaN==otherNaN);
//--- resultado
   NaN>10.3=false
   NaN<-5=false
   (NaN==0)=false
   (NaN==NaN)=false
//---
  }

Por eso, si queremos utilizar estas funciones en el estilo MQL4, solo deberemos realizar operaciones comerciales (como cualquier otra acción importante) si el resultado de la comparación es true.  Aunque, personalmente, en este caso, nosotros insistimos en comprobar el valor retornado con la función MathIsValidNumber

Identificadores de las líneas de los indicadores en MQL4 y MQL5

Existe un problema de compatibilidad en parte de los valores de las constantes que describen las líneas de los indicadores. Por ejemplo, tomemos iAlligator:

  • MQL4: 1 - MODE_GATORJAW, 2 - MODE_GATORTEETH, 3 - MODE_GATORLIPS
  • MQL5: 0 - GATORJAW_LINE,   1 - GATORTEETH_LINE,   2 - GATORLIPS_LINE 

El problema es que al final, en la función "IndicatorsXXXX.mqh", la línea del indicador llega como una cifra. Y si se trata de una cifra, por ejemplo, igual a 1, entonces nadie podrá decir a qué se refería el usuario: si estaba trabajando en el estilo MQL4 (y se refería a 1 - MODE_GATORJAW), o si estaba trabajando en el estilo MQL5 (y se refería a otra línea de indicador 1 completamente distinta - GATORTEETH_LINE).

En relación con esto, hemos decidido crear dos archivos de inclsuión, prácticamente gemelos: "IndicatorsMQL4.mqh" y "IndicatorsMQL5.mqh". Su diferencia reside en que el archivo "IndicatorsMQL4.mqh" comprende las líneas de los indicadores SOLO en el estilo MQL4, mientras que el archivo "IndicatorsMQL5.mqh", SOLO en el estilo MQL5. Además, en este caso, en "IndicatorsMQL4.mqh", la transformación de la línea de indicador en el parámetro de entrada se realiza directamente dentro de las funciones iADX, iAlligator ... — no es posible sacar esta transformación a #define .

Vamos a ver el motivo de esta prohibición usando el ejemplo de iBands y iEnvelopes:

//+------------------------------------------------------------------+
//| iBands function in MQL4 notation                                 |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN, 1 - MODE_UPPER, 2 - MODE_LOWER          |
//|      MQL5 0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND          |
//+------------------------------------------------------------------+
double   iBands(
...
//+------------------------------------------------------------------+
//| iEnvelopes function in MQL4 notation                             |
//|   The buffer numbers are the following:                          |
//|      MQL4 0 - MODE_MAIN,  1 - MODE_UPPER, 2 - MODE_LOWER         | ???
//|      MQL5 0 - UPPER_LINE, 1 - LOWER_LINE,        -/-             |
//+------------------------------------------------------------------+
double   iEnvelopes(

En MQL4, MODE_UPPER para el indicador Bands se transforma en 1, y para el indicador Envelopes, en 0.

2. ¿Cuál es la situación del gasto de memoria, cuando en cada tick se trabaja con los indicadores en el estilo MQL4?

Vamos a comprar el gasto de memoria de dos asesores: "iMACD.mq5", un asesor con acceso correcto a los indicadores, y "MACD MQL4 style EA short.mq5", un asesor con acceso a los indicadores en el estilo MQL4. En los ajustes del terminal, el máximo de barras en la ventana se ha dejado con el valor "100 000". Vamos a crear dos perfiles de 14 gráficos:

  • el perfil "iMACd" de 13 gráficos se ha fijado al asesor "iMACd.mq5", los 13 gráficos tienen el marco temporal М30;
  • el perfil "MACD MQL4 style EA short" con 13 gráficos se ha fijado al asesor "MACD MQL4 style EA short.mq5".

En el decimocuarto gráfico se encontrará el indicador "Terminal memory used.mq5", que cada 10 segundos imprime el identificador TERMINAL_MEMORY_USED.

Vamos a comparar dos resultados: cuánta memoria (RAM) consume el terminal (datos del administrador de tareas) y el identificador imprimido TERMINAL_MEMORY_USED. La observación se realizará durante 10 minutos, por lo que veremos si la memoria empieza a consumirse. La condición principal comprende que después del inicio del terminal no haya que hacer nada en el mismo, ni abrir nuevas pestañas, ni leer el chat.

Perfil Administrador de tareas TERMINAL_MEMORY_USED Administrador de tareas (dentro de 10 minutos) TERMINAL_MEMORY_USED (dentro de 10 minutos)
iMACd 279.7 Mb 745 Mb 279.7 Mb 745 Mb
MACD MQL4 style EA short 279.9 Mb 745 Mb 280.0 Mb 745 Mb

Ahora vamos a cambiar el aspecto de la simulación: tras 10 minutos de funcionamiento, pasamos el marco temporal de los 13 gráficos a H1.

Perfil Administrador de tareas TERMINAL_MEMORY_USED Administrador de tareas (dentro de 10 minutos) TERMINAL_MEMORY_USED (dentro de 10 minutos)
iMACd 398.0 Mb 869 Mb 398.3 Mb 869 Mb
MACD MQL4 style EA short 319.2 Mb 874 Mb 330.5 Mb 874 Mb

Recuadro final, en este se ve con mayor claridad el uso de la memoria:

Perfil Administrador de tareas
(M30), Mb
TERMINAL_MEMORY_USED
(M30), Mb
Administrador de tareas
(H1), Mb
TERMINAL_MEMORY_USED
(H1), Mb

inicio dentro de 10 min. inicio dentro de 10 min. inicio dentro de 10 min. inicio dentro de 10 min.
iMACd 279.7 279.7 745 745 398.0 869 398.3 869
MACD MQL4 style EA short 279.9 280.0 745 745 319.2 874 330.5 874

3. Nueva vida del asesor MACD Sample.mq4

Vamos a comprobar la velocidad de ejecución, el gasto de memoria y la correspondencia del comercio del asesor [data folder]\MQL4\Experts\MACD Sample.mq4 (que escribiremos en MQL5, pero en el estilo MQL4, como "MACD MQL4 style EA short.mq5") y el asesor [data folder]\MQL5\Experts\Examples\MACD\MACD Sample.mq5.

3.1. Modificamos el asesor "MACD Sample.mq5": solo obtendremos un valor cada vez

"MACD Sample.mq5" del paquete estándar recibe al mismo tiempo dos valores del indicador:

//+------------------------------------------------------------------+
//| main function returns true if any position processed             |
//+------------------------------------------------------------------+
bool CSampleExpert::Processing(void)
  {
//--- refresh rates
   if(!m_symbol.RefreshRates())
      return(false);
//--- refresh indicators
   if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2)
      return(false);
   if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main)  !=2 ||
      CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 ||
      CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA)         !=2)
      return(false);
//   m_indicators.Refresh();
//--- to simplify the coding and speed up access
//--- data are put into internal variables
   m_macd_current   =m_buff_MACD_main[0];
   m_macd_previous  =m_buff_MACD_main[1];
   m_signal_current =m_buff_MACD_signal[0];
   m_signal_previous=m_buff_MACD_signal[1];
   m_ema_current    =m_buff_EMA[0];
   m_ema_previous   =m_buff_EMA[1];

Después de ello, a las variables se les asignan los datos de las matrices con una dimensión "2". ¿Por qué se hace precisamente de esta forma? Resulta obvio que, al copiar un valor, ya sea solo uno o dos a la vez, usaremos CopyBuffer. Pero al copiar dos valores de una sola vez, ahorraremos una operación de anotación en la matriz.

Sin embargo, el asesor "MACD Sample.mq4" solo obtiene un valor del indicador de una sola vez:

//--- to simplify the coding and speed up access data are put into internal variables
   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);

Se interroga dos veces la línea principal de MACD, dos veces la línea de señal MACD y dos veces Moving Average. Por eso debemos darle al asesor "MACD Sample.mq5" el mismo aspecto. Vamos a llamar esta versión del asesor "MACD Sample One value at a time.mq5". Aquí tenemos su modificación, en la cual obtenemos un valor cada vez:

//--- refresh indicators
   if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2)
      return(false);
//   if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main)  !=2 ||
//      CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 ||
//      CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA)         !=2)
//      return(false);
//   m_indicators.Refresh();
//--- to simplify the coding and speed up access
//--- data are put into internal variables
   CopyBuffer(m_handle_macd,0,0,1,m_buff_MACD_main);
   m_macd_current=m_buff_MACD_main[0];
   CopyBuffer(m_handle_macd,0,1,1,m_buff_MACD_main);
   m_macd_previous=m_buff_MACD_main[0];
   CopyBuffer(m_handle_macd,1,0,1,m_buff_MACD_signal);
   m_signal_current=m_buff_MACD_signal[0];
   CopyBuffer(m_handle_macd,1,1,1,m_buff_MACD_signal);
   m_signal_previous=m_buff_MACD_signal[0];
   CopyBuffer(m_handle_ema,0,0,1,m_buff_EMA);
   m_ema_current=m_buff_EMA[0];
   CopyBuffer(m_handle_ema,0,1,1,m_buff_EMA);
   m_ema_previous=m_buff_EMA[0];

Este código se guarda en el asesor "MACD Sample One value at a time.mq5", adjunto al artículo.

3.2. Transformamos el asesor "MACD Sample.mq4" en código MQL5

Para que en el asesor podamos recurrir a los indicadores en el estilo MQL4, y también para trabajar con las posiciones y para poder comerciar, incluiremos el archivo "IndicatorsMQL4.mqh" (recordemos que este archivo, al trabajar con indicadores, solo entiende los nombres MQL4 de las líneas de indicador) y las clases comerciales CPositionInfo, CTrade, CSymbolInfo y CAccountInfo. Asimismo, para recurrir adecuadamente a los indicadores en "IndicatorsMQL4.mqh", es necesario añadir al asesor un bloque de "defines": los nombres de las líneas de indicador:

#property description " and the indicators are accessed in the style of MQL4"
#define MODE_MAIN    0
#define MODE_SIGNAL  1 
#include <SimpleCall\IndicatorsMQL4.mqh>
//---
#include <Trade\PositionInfo.mqh>
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>  
#include <Trade\AccountInfo.mqh>
CPositionInfo  m_position;                   // trade position object
CTrade         m_trade;                      // trading object
CSymbolInfo    m_symbol;                     // symbol info object
CAccountInfo   m_account;                    // account info wrapper
//---
input double TakeProfit    =50;

Asimismo, para implementar la construcción según las cotizaciones de tres o cinco dígitos, necesitaremos un multiplicador especial:

input double MACDCloseLevel=2;
input int    MATrendPeriod =26;
//---
double       m_adjusted_point;               // point value adjusted for 3 or 5 points
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

Para obtener los precios actuales, nosotros usaremos el objeto m_symbol de la clase comercial CSymbolInfo:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!m_symbol.Name(Symbol())) // sets symbol name
      return(INIT_FAILED);
   RefreshRates();

El método RefreshRates() actualiza los precios y controla que no haya precios con el valor "0.0":

//+------------------------------------------------------------------+
//| Refreshes the symbol quotes data                                 |
//+------------------------------------------------------------------+
bool RefreshRates(void)
  {
//--- refresh rates
   if(!m_symbol.RefreshRates())
     {
      Print("RefreshRates error");
      return(false);
     }
//--- protection against the return value of "zero"
   if(m_symbol.Ask()==0 || m_symbol.Bid()==0)
      return(false);
//---
   return(true);
  }

La inicialización del multiplicador m_adjusted_point se realiza en OnInit(), después de inicializar el objeto m_symbol:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!m_symbol.Name(Symbol())) // sets symbol name
      return(INIT_FAILED);
   RefreshRates();
   //--- tuning for 3 or 5 digits
   int digits_adjust=1;
   if(m_symbol.Digits()==3 || m_symbol.Digits()==5)
      digits_adjust=10;
   m_adjusted_point=m_symbol.Point()*digits_adjust;
//---
   return(INIT_SUCCEEDED);
  }

En OnTick(), gracias al archivo de inclusión "IndicatorsMQL4Style.mqh", recurrimos a los indicadores en el estilo MQL4:

   if(!RefreshRates())
      return;
//--- to simplify the coding and speed up access data are put into internal variables
   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);

3.2.1. Trabajando con las pocisiones

Para que se correspondan al máximo, determinaremos la ausencia de posiciones como

   total=PositionsTotal();
   if(total<1)
     {

Aunque esta aproximación no es totalmente correcta, puesto que en ella no se tiene en cuenta la presencia de posiciones de otros símbolos y/o con otros identificadores (números mágicos).

3.2.2. Las posiciones Buy se abren con la ayuda del método Buy de la clase comercial CTrade, mientras que la corrección de la ejecución se comprueba con la ayuda del método ResultDeal de esta misma clase. ResultDeal retorna el ticket de la transacción, si esta ha finalizado.

      //--- check for long position (BUY) possibility
      if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && 
         MathAbs(MacdCurrent)>(MACDOpenLevel*m_adjusted_point) && MaCurrent>MaPrevious)
        {
         m_trade.Buy(Lots,m_symbol.Name(),m_symbol.Ask(),
                     0.0,
                     m_symbol.NormalizePrice(m_symbol.Ask()+TakeProfit*m_adjusted_point),
                     "macd sample");
         if(m_trade.ResultDeal()!=0)
            Print("BUY position opened : ",m_trade.ResultPrice());
         else
            Print("Error opening BUY position : ",m_trade.ResultRetcodeDescription());
         return;
        }

Preste atención a que el precio en la solicitud comercial se normaliza con el método NormalizePrice de la clase comercial CSymbolInfo. Este método permite tener en cuenta la cuantificación: el cambio mínimo del precio y el número de decimales tras el punto. 

Para abrir una posición Sell, usaremos los métodos análogos.

3.2.3. Bloque de iteración de posiciones: cierre o modificación.

El propio ciclo se itera partiendo del número total de posiciones menos uno y hasta el cero incluido. Para que sea posible trabajar con las posiciones, primero deberemos elegir una según su índice en la lista general:

   for(int i=PositionsTotal()-1;i>=0;i--)
      if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties

El cierre de posición se lleva a cabo con el método PositionClose, y la modificación, con PositionModify. Preste atención a que, al realizar la modificación, se usa de nuevo el método de normalización de precios NormalizePrice de la clase comercial CSymbolInfo.

Aquí tenemos el bloque completo de iteración de la posición:

//--- it is important to enter the market correctly, but it is more important to exit it correctly...   
   for(int i=PositionsTotal()-1;i>=0;i--)
      if(m_position.SelectByIndex(i)) // selects the position by index for further access to its properties
         if(m_position.Symbol()==m_symbol.Name())
           {
            //--- long position is opened
            if(m_position.PositionType()==POSITION_TYPE_BUY)
              {
               //--- should it be closed?
               if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
                  MacdCurrent>(MACDCloseLevel*m_adjusted_point))
                 {
                  //--- close position and exit
                  if(!m_trade.PositionClose(m_position.Ticket()))
                     Print("PositionClose error ",m_trade.ResultRetcodeDescription());
                  return;
                 }
               //--- check for trailing stop
               if(TrailingStop>0)
                 {
                  if(m_position.PriceCurrent()-m_position.PriceOpen()>m_adjusted_point*TrailingStop)
                    {
                     if(m_position.StopLoss()<m_symbol.Bid()-m_adjusted_point*TrailingStop)
                       {
                        //--- modify position and exit
                        if(!m_trade.PositionModify(m_position.Ticket(),
                           m_symbol.NormalizePrice(m_position.PriceCurrent()-m_adjusted_point*TrailingStop),
                           m_position.TakeProfit()))
                           Print("PositionModify error ",m_trade.ResultRetcodeDescription());
                        return;
                       }
                    }
                 }
              }

            if(m_position.PositionType()==POSITION_TYPE_SELL)
              {
               //--- should it be closed?
               if(MacdCurrent<0 && MacdCurrent>SignalCurrent && 
                  MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*m_adjusted_point))
                 {
                  //--- close position and exit
                  if(!m_trade.PositionClose(m_position.Ticket()))
                     Print("PositionClose error ",m_trade.ResultRetcodeDescription());
                  return;
                 }
               //--- check for trailing stop
               if(TrailingStop>0)
                 {
                  if((m_position.PriceOpen()-m_position.PriceCurrent())>(m_adjusted_point*TrailingStop))
                    {
                     if((m_position.StopLoss()>(m_symbol.Ask()+m_adjusted_point*TrailingStop)) || (m_position.StopLoss()==0.0))
                       {
                        //--- modify position and exit
                        if(!m_trade.PositionModify(m_position.Ticket(),
                           m_symbol.NormalizePrice(m_symbol.Ask()+m_adjusted_point*TrailingStop),
                           m_position.TakeProfit()))
                           Print("PositionModify error ",m_trade.ResultRetcodeDescription());
                        return;
                       }
                    }
                 }
              }
           }

Estos son todos los cambios. El archivo final "MACD Sample 4 to 5 MQL4 style.mq5" se adjunta al final de este artículo.

3.3. Vamos a comparar la velocidad de ejecución de los asesores basados en MACD

En la comparación participarán:

  • "MACD Sample.mq5" — asesor del paquete estándar con acceso correcto a los indicadores
  • "MACD Sample One value at a time.mq5" — análogo de "MACD Sample.mq5", en el que obtenemos un valor de los indicadores de una sola vez
  • "MACD Sample 4 to 5 MQL4 style.mq5" — asesor MQL4, reescrito en MQL5 con modificaciones mínimas y con acceso a los indicadores en el estilo MQL4

La simulación se ha realizado en USDJPY,M30 desde el 2017.02.01 al 2018.01.16, en el servidor MetaQuotes-Demo. Después de cada simulación (ya sea con el cambio de asesor o con el cambio de modo de generación de ticks), el terminal ha sido reiniciado. Configuración de la computadora:

Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M  @ 2.50GHz, Memory: 4217 / 8077 Mb, Disk: 335 / 464 Gb, GMT+2

№ ordinal Asesor Experto Cada tick basado en ticks reales Todos los ticks OHLC


Hora de la simulación Operaciones Transacciones Hora de la simulación Operaciones Transacciones Hora de la simulación Operaciones Transacciones
 1  MACD Sample.mq5  0:01:19.485  122  244  0:00:53.750  122  244  0:00:03.735  119  238
 2  MACD Sample One value at a time.mq5  0:01:20.344  122  244  0:00:56.297  122  244  0:00:03.687  119  238
 3  MACD Sample 4 to 5 MQL4 style.mq5  0:02:37.422  122  244  0:01:52.171  122  244  0:00:06.312  119  238

Los tres asesores han mostrado gráficos idénticos en el modo "Todos los ticks":

MACD Sample

Fig. 2. MACD Sample XXXX en el simulador de estrategias


CONCLUSIÓN: el asesor "MACD Sample 4 to 5 MQL4 style.mq5", con acceso a los indicadores en el estilo MQL4, pierde por dos veces en velocidad en comparación con los asesores análogos con acceso correcto a los indicadores.

3.4. Vamos a comparar el gasto de memoria de los asesores basados en MACD

Para ello, se usarán los mismos 14 gráficos que en el punto 2. ¿Qué sucederá con el gasto de memoria si trabajamos en cada tick con los indicadores en el estilo MQL4? En el primer gráfico permanece inevitablemente el indicador "Terminal memory used.mq5", que cada 10 segundos imprime el identificador TERMINAL_MEMORY_USED, y en los 13 restantes se fijan por turno los asesores. Antes de cada medida, el terminal se reinicia.

№ ordinal Asesor Experto Administrador de tareas, Mb TERMINAL_MEMORY_USED, Mb
 1  MACD Sample.mq5  334.6  813
 2  MACD Sample One value at a time.mq5  335.8  813
 3  MACD Sample 4 to 5 MQL4 style.mq5  342.2  818

CONCLUSIÓN: En cuanto al gasto de memoria, los asesores basados en MACD con acceso correcto a los indicadores y el asesor basado en MACD con acceso a los indicadores en el estilo MQL4 son comparables. Es decir, consumen más o menos la misma memoria.

4. Nueva vida del asesor [data folder]\MQL4\Experts\Moving Average.mq4

Si bien en el capítulo 3 transformamos MQL4 en MQL5, en el caso del asesor Movinge Average.mq4, proponemos simplemente modificar el asesor Moving Average.mq5 mediante la inclusión del archivo "IndicatorsMQL5.mqh"

#property version   "1.00"
#include <SimpleCall\IndicatorsMQL5.mqh>

#include <Trade\Trade.mqh>

y las sustituciones de CopyBuffer

//--- get current Moving Average 
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }

al estilo MQL4 para recurrir a los indicadores:

//--- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);

El acceso a los indicadores en el estilo MQL4 solo nos deja una posibilidad de comprobar el resultado de la operación: comparar los datos recibidos con cero. Teniendo en cuenta este factor, la anotación en los bloques "CheckForOpen" y "CheckForClose" era la siguiente:

//--- get current Moving Average 
   double   ma[1];
   if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }

y ahora será como sigue:

//--- get current Moving Average 
   double   ma[1];
   ma[0]=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//if(CopyBuffer(ExtHandle,0,0,1,ma)!=1)
   if(ma[0]==0.0)
     {
      //Print("CopyBuffer from iMA failed, no data");
      Print("Get iMA in MQL4 style failed, no data");
      return;
     }

Todos estos cambios los guardaremos en el asesor "Moving Average MQL4 style.mq5". El asesor se adjunta al final del artículo. Vamos a medir el rendimiento y el consumo de memoria de "Moving Average.mq5" estándar y "Moving Average MQL4 style.mq5". 

Recordemos que las simulaciones se han realizado en el equipo

Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M  @ 2.50GHz, Memory: 4217 / 8077 Mb, Disk: 335 / 464 Gb, GMT+2

 y que tras cada simulación el terminal ha sido reinicializado. Se ha simulado EURUSD,M15 desde el 2017.02.01 al 2018.01.16 en el servidor MetaQuotes-Demo.

№ ordinal Asesor Experto Cada tick basado en ticks reales Todos los ticks OHLC


Hora de la simulación Operaciones Transacciones Hora de la simulación Operaciones Transacciones Hora de la simulación Operaciones Transacciones
 1  Moving Average.mq5  0:00:33.359  1135  2270  0:00:22.562  1114  2228  0:00:02.531  1114  2228
 2  Moving Average MQL4 style.mq5  0:00:34.984  1135  2270  0:00:23.750  1114  2228  0:00:02.578  1114  2228

CONCLUSIÓN: Es probable que en MACD Sample, durante el acceso a los indicadores en estilo MQL4, el núcleo MQL5 se haya visto obligado a realizar la búsqueda entre dos manejadores, y que precisamente en esa búsqueda se haya gastado tiempo.

En el caso del asesor Moving Average, al acceder al indicador en el estilo MQL4, el núcleo MQL5 no gasta tiempo en la búsqueda del manejador adecuado, puesto que es el único. 

Vamos a comparar el gasto de memoria de los asesores basados en Moving Average

Para ello, se usarán los mismos 14 gráficos que en el punto 2. En el primer gráfico permanece inevitablemente el indicador "Terminal memory used.mq5", que cada 10 segundos imprime el identificador TERMINAL_MEMORY_USED, y en los 13 restantes se fijan por turno los asesores. Antes de cada medida, el terminal se reinicia.

№ ordinal Asesor Experto Administrador de tareas, Mb TERMINAL_MEMORY_USED, Mb
 1  Moving Average.mq5  295.6  771
 2  Moving Average MQL4 style.mq5  283.6  760

CONCLUSIÓN: El gasto de memoria es prácticamente idéntico. Podemos atribuir ciertas divergencias a la "vida interior" del terminal: la actualización de noticias, etcétera.

5. Análogo de las series iXXXX

Ya que hemos implementado la obtención de los valores de los indicadores en el estilo MQL4, vamos a escribir de paso las funciones del apartado Acceso a las series temporales e indicadores. La implementación se encontrará en [data folder]\MQL5\Include\SimpleCall\Series.mqh.

Lista de funciones en "Series.mqh" que posibilitan el acceso a los valores de las series temporales, como en MQL4:


Para las funciones iHighest y iLowest están disponibles los identificadores predeterminados de las series MODE_OPEN, MODE_LOW, MODE_HIGH, MODE_CLOSE, MODE_VOLUME, MODE_TIME.

Ejemplo de implementación de la función iClose:

//+------------------------------------------------------------------+
//| iClose function in MQL4 notation                                 |
//+------------------------------------------------------------------+
double   iClose(
                string                    symbol,              // symbol
                ENUM_TIMEFRAMES           timeframe,           // timeframe
                int                       shift                // shift
                )
  {
   double result=0.0;
//---
   double val[1];
   ResetLastError();
   int copied=CopyClose(symbol,timeframe,shift,1,val);
   if(copied>0)
      result=val[0];
   else
      Print(__FUNCTION__,": CopyClose error=",GetLastError());
//---
   return(result);
  }

El valor del precio de cierre de la barra shift lo obtenemos con la ayuda de CopyClose, la primera forma de la llamada (acceso según la posición inicial y el número de elementos necesarios):

int  CopyClose( 
   string           symbol_name,       // nombre del símbolo 
   ENUM_TIMEFRAMES  timeframe,         // periodo 
   int              start_pos,         // desde dónde comenzamos  
   int              count,             // cuánto copiamos 
   double           close_array[]      // matriz para el copiado de precios de cierre 
   );

Conclusión

Como podemos ver, MQL5 permite a los devotos de MQL4 obtener los valores de los indicadores y las series temporales en su estilo preferido. Dichos tráders aseguran que este código es más corto y se lee con mayor facilidad. Los desarrolladores de la plataforma, a su vez, exigen un trabajo más escrupuloso con el código y el máximo de comprobaciones al llamar las funciones (estamos completamente de acuerdo con ellos). Vamos a enumerar brevemente las ventajas y desventajas analizadas en el artículo.


Desventajas:
  • limitación en el procesamiento del error retornado al acceder a los indicadores;
  • disminución de la velocidad de simulación al acceder simultáneamente a más de un indicador;
  • necesidad de indicar correctamente las líneas de los indicadores, dependiendo de la inclusión de "IndicatorsMQL5.mqh" o "IndicatorsMQL4.mqh".
Ventajas
  • sencillez de escritura del código: una línea en lugar de varias;
  • claridad y brevedad: cuanto menos código, más sencillo resulta entenderlo.
Sin embargo, nosotros nos mantenemos fieles al enfoque MQL5 clásico en cuanto al acceso a los indicadores: en este artículo solo hemos puesto a prueba una variante alternativa.

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

Archivos adjuntos |
MQL5.zip (26.02 KB)
Simulador de estrategias personalizado basado en cálculos matemáticos rápidos Simulador de estrategias personalizado basado en cálculos matemáticos rápidos

El artículo describe el proceso de construcción de un simulador de estrategias personalizado y un analizador de pasadas de optimización de creación propia. Después de leerlo, usted entenderá cómo funciona el modo de cálculos matemáticos y el mecanismo de los llamados frames; también aprenderá a preparar y cargar sus propios datos para los cálculos y a utilizar algoritmos eficientes para la compresión de los mismos. Además, este artículo será de interés para cualquier persona interesada en las distintas formas de almacenamiento de la información del usuario en un experto.

Cómo reducir los riesgos del tráder Cómo reducir los riesgos del tráder

El comercio en los mercados financieros se relaciona con una serie de riesgos que deben ser tenidos en cuenta en los algoritmos de los sistemas comerciales. La reducción de dichos riesgos es una tarea vital a la hora de obtener beneficios en el trading.

Gestión de capital según Vince. Implementación como módulo de Wizard MQL5 Gestión de capital según Vince. Implementación como módulo de Wizard MQL5

El artículo se basa en el libro de R.Vince "Las matemáticas de la gestión de capital". En este se analizan los métodos empíricos y paramétricos usados para localizar el tamaño óptimo de lote comercial, sobre cuyas bases se han escrito los módulos comerciales de gestión de capital para el wizard MLQ5.

Patrón de ruptura del canal Patrón de ruptura del canal

Como se sabe, los canales de precios se forman por las tendencias de precios. Una de las señales más fuertes del cambio de la tendencia es la ruptura del canal actual. En este artículo, yo propongo intentar automatizar el proceso de la búsqueda de las señales de este tipo, y ver si es posible formar su propia estrategia a base de eso.