Descargar MetaTrader 5

¿Le ha gustado el artículo?
Ponga el enlace al artículo -
que los demás también lo lean

Utilice nuevas posibilidades de MetaTrader 5

Guía paso a paso para escribir un Expert Advisor en MQL5 para principiantes

17 diciembre 2013, 15:49
Samuel Olowoyo
0
20 029

Introducción

Este artículo está dirigido a los principiantes que quieren aprender a escribir Expert Advisors sencillos en el nuevo lenguaje MQL5. En primer lugar, vamos a definir lo que se requiere de nuestro EA (de sus siglas en inglés, Expert Advisor), y luego a cómo queremos que lo haga.


1. Estrategia de trading

Lo que va hacer nuestro EA:

  • Hará el seguimiento de un indicador concreto, y cuando se cumpla una determinada condición (o condiciones), hará una operación de trading (ya sea de Compra/Corto o Venta/Largo), dependiendo de la condición que se haya cumplido.

A esto se le conoce como estrategia de trading. Antes de que puedas escribir un EA, debes primero desarrollar la estrategia que quieres automatizar en un EA. Así que vamos a modificar, en este caso, la afirmación anterior para que refleje la estrategia que queremos desarrollar en un EA.

  • Usaremos el indicador de Promedio Móvil "Moving Average", con un período 8 (puedes elegir cualquier período, pero para el propósito de nuestra estrategia, usaremos 8).

  • Queremos que nuestro EA haga una operación de trading Larga (Compra) "Long (Buy)" cuando el Moving Average-8 (que llamaremos MA-8, para simplificar) aumenta al alza y el precio de cierre está por encima de él, y que haga una operación de trading Corta (Venta ) "Short (Sell)" cuando el MA-8 cae decreciendo y el precio de cierre está por debajo de él.
  • Utilizaremos también otro indicador llamado Índice de Movimiento Direccional Medio "Average Directional Movement Index" (ADX) con un período 8 también para ayudarnos a determinar si hay una tendencia en el mercado. Hacemos esto, porque lo único que queremos es realizar transacciones cuando haya una tendencia en el mercado, y relajarnos cuando el mercado está sin tendencia. Para conseguirlo, sólo tendremos que poner nuestra transacción (Compra o Venta) cuando se cumplan las condiciones mencionadas anteriormente y el que valor de ADX sea superior a 22. Si ADX es superior a 22 pero decreciendo, o ADX es inferior a 22, no haremos transacciones, aunque se haya cumplido la condición B.
  • Además, queremos protegernos y minimizar las perdidas, estableciendo Stop Loss a 30 puntos, y para nuestro objetivo de beneficios, ponemos Take Profit a 100 puntos.
  • Queremos también que nuestro EA busque oportunidades de Compra/Venta únicamente cuando se forma una nueva barra, además de asegurarnos de abrir una posición de Compra si se cumplen las condiciones de Compra y que no hayan posiciones abiertas, y abrir una posición de Venta si se cumplen las condiciones de Venta y que hayan posiciones abiertas.

Nuestra estrategia ya está desarrollada, ahora es el momento de empezar a escribir nuestro código.


2. Escribir un Expert Advisor

2.1 MQL5 Wizard (asistente)

Empieza con la ejecución de MetaQuotes Language Editor 5. A continuación presiona Ctrl+N o haz clic en el botón Nuevo en la barra de del menú

Figura 1. Crear un nuevo documento MQL5

Figura 1. Crear un nuevo documento MQL5

 En la ventana de MQL5 Wizard, selecciona Expert Advisor y haz clic en "Siguiente" como se muestra en la Fig.2:

Figura 2. Selección del tipo de documento

Figura 2. Selección del tipo de programa

En la siguiente ventana, escribe el nombre que quieres dar a tu EA en el campo Nombre. En este caso, he puesto My_First_EA. A continuación escribe tu nombre en el campo Autor, así que la dirección de tu sitio web o correo electrónico en el campo Enlace (si la tienes).

Figura 3. Propiedades generales del Expert Advisor

Figura 3. Propiedades generales del Expert Advisor

Puesto que queremos poder cambiar algunos de los parámetros de nuestro EA, con el objetivo de saber cuál es el valor que nos puede dar el mejor resultado, los vamos a añadir haciendo clic en el botón "Añadir".

Figura 4. Ajustes de los parámetros de entrada de EA

Figura 4. Ajustes de los parámetros de entrada de EA

En nuestro EA, queremos poder hacer pruebas con nuestro ajustes de Stop Loss, Take Profit, Período de ADX y Período de Moving Average, por ello los vamos a definir en este punto.

Haz un doble clic en la columna Nombre y escribe el nombre del parámetro, a continuación, haz un doble clic sobre el Tipo para elegir el tipo de datos del parámetro, y luego haz doble clic sobre la columna del Valor inicial y escribe el valor inicial del parámetro.

Una vez hayas terminado, deberías ver algo como esto:

Figura 5. Parámetros de entrada de los tipos de datos del EA

Figura 5. Parámetros de entrada de los tipos de datos del EA

Como puedes ver más arriba, he seleccionado datos de tipo entero (int) para todos los parámetros. Hablemos un poco sobre los tipos de datos.

  • char: El tipo char ocupa 1 byte de memoria (8 bits) y permite expresar en notación binaria 2^8=256 valores. El tipo char puede tener valores positivos y negativos. Sus valores están entre -128 y 127.
  • uchar : El tipo entero uchar ocupa también 1 byte de memoria, igual que el tipo char, pero a diferencia de este, uchar está destinado únicamente a los valores positivos. El valor mínimo es cero, el valor máximo es 255. La primera letra u en el nombre del tipo uchar es la abreviatura de unsigned.
  • short: El tamaño del tipo short es de 2 bytes (16 bits) y, por consiguiente, permite expresar un rango de valores igual a 2 elevado a 16: 2^16 = 65 536. Dado que el tipo short tiene signo, y puede tener valores positivos y negativos, tiene un rango de valores entre -32 768 y 32 767.
  • ushort: El tipo short sin signo es el tipo ushort, que también tiene un tamaño de 2 bytes. El valor mínimo es 0, el valor máximo es 65 535.
  • int: El tamaño del tipo int es de 4 bytes (32 bits). El valor mínimo es -2 147 483 648 y el máximo es 2 147 483 647.
  • uint:  El tipo entero sin signo es el uint. Ocupa 4 bytes de memoria y permite expresar enteros desde 0 hasta 4 294 967 295.
  • long: El tamaño del tipo long es de 8 bytes (64 bits). El valor mínimo es -9 223 372 036 854 775 808, el valor máximo es 9 223 372 036 854 775 807.
  • ulong: El tipo ulong también ocupa 8 bytes y puede almacenar valores desde 0 hasta 18 446 744 073 709 551 615.

Como puede verse en la descripción de los distintos tipos de datos, los tipos enteros sin signo no están diseñados para almacenar valores negativos, cualquier intento de asignarles valores negativos puede llevar a unos resultados impredecibles. Si quieres almacenar valores negativos, no lo puedes hacer en los tipos sin signo (es decir, uchar, uint, ushort, ulong).

Volvamos a nuestro EA. Con respeto a los tipos de datos, estarás de acuerdo conmigo que se supone que tenemos que utilizar los tipos de datos char o uchar, ya que los datos que pretendemos almacenar en ellos son inferiores a 127 y 255 respectivamente. Para una buena administración de la memoria, es la mejor opción. Pero aún así, por conveniencia, escogeremos el tipo int.

Un vez hayas terminado de configurar todos los parámetros necesarios, pulsa el botón Finalizar, y MetaQuotes Editor te va a generar la estructura del código, como se muestra en la siguiente figura.


Para una mejor comprensión, vamos a dividir el código en diferentes secciones.

En la parte superior del código (encabezado) están definidas las propiedades del EA. Como puedes ver, estos son los valores que se especificaron en el MQL5 Wizard en la figura 3. 

En esta sección del código, puedes especificar parámetros adicionales, como description (una breve descripción del EA), declarar constantes, incluir archivos adicionales o importar funciones. 


Cuando una declaración empieza con el símbolo #, se le llama directiva de preprocesador y no acaba con punto y coma ‘;’ a continuación, otro ejemplo de directivas de preprocesador:

#define : 

La directiva #define se usa para la declaración de constantes. Se escribe así

#define identifier token_string

Lo que hace es remplazar identifier cada vez que aparece en tu código por el valor de token_string.

Ejemplo:

#define ABC               100
#define COMPANY_NAME      "MetaQuotes Software Corp."

Se reemplazará cada aparición de COMPANY_NAME con la cadena "MetaQuotes Software Corp." o reemplazará cada aparición de ABC por el char (o entero) 100 en tu código.

Puedes encontrar más detalles sobre las directivas de preprocesador en el Manual de MQL5. Vamos a seguir.

La segunda parte del encabezado de nuestro código es la sección parámetros de entrada:

 

En esta sección, especificamos todos parámetros que usaremos en nuestro EA. Esto incluye todas las variables que van a ser utilizadas por todas las funciones que escribiremos en nuestro EA.

Las variables declaradas en este nivel se llaman Variables globales ya que están disponibles para cualquier función que los pueda requerir en nuestro EA. Los parámetros de entrada son parámetros que se pueden cambiar únicamente fuera de nuestro EA. También podemos declarar otras variables, que manejaremos a lo largo de nuestro EA, pero en esta sección no van a estar disponibles fuera de nuestro EA. 

Lo siguiente es la función de inicialización del EA. Es la primera función a la que se llama cuando se ejecuta nuestro EA o se vincula a un gráfico, y se le llama una sola vez. 


Esta sección es el mejor sitio para hacer algunas comprobaciones importantes para asegurarnos de que nuestro EA funciona muy bien.

Podemos comprobar si hay suficientes barras para que nuestro EA funcione, etc.

Representa también el mejor sitio para obtener los identificadores que usaremos con nuestros indicadores (indicadores ADX y Moving Average).

 
 Se llama a la función OnDeinit cuando se elimina el EA del gráfico.

Para nuestro EA, lanzaremos los identificadores creados para nuestros indicadores durante la inicialización en esta sección.


Esta función procesa el evento Newtick, que se genera cuando se recibe una nueva cotización para un símbolo. 

Ten en cuenta, que el Expert Advisor no puede realizar operaciones de trading si no está autorizado el uso del Expert Advisor en el terminal del cliente (Botón "Auto Trading").

Figura 6. El trading automático está puesto en marcha

Figura 6. El trading automático está puesto en marcha

En esta sección, se va a escribir la mayor parte de nuestros códigos que va a implementar nuestra estrategia de trading, desarrollada anteriormente.

Ahora que hemos visto las diferentes secciones del código de nuestro EA, vamos a empezar a afinar los detalles de la estructura.

2.2 SECCIÓN DE LOS PARÁMETROS DE ENTRADA

//--- parámetros de entrada
input int      StopLoss=30;      // Stop Loss
input int      TakeProfit=100;   // Take Profit
input int      ADX_Period=8;     // Período ADX
input int      MA_Period=8;      // Período Moving Average
input int      EA_Magic=12345;   // Magic Number EA
input double   Adx_Min=22.0;     // El valor mínimo de ADX
input double   Lot=0.1;          // Lotes de trading
//--- Otros parámetros
int adxHandle; // identificador para nuestro indicador ADX
int maHandle;  // identificador para nuestro indicador Moving Average
double plsDI[],minDI[],adxVal[]; // Arrays dinámicos para guardar los valores de +DI, -DI y los valores de ADX para cada barra
double maVal[]; // Array dinámico para guardar los valores de Moving Average para cada barra
double p_close; // Variable para almacenar el valor de la barra de cierre
int STP, TKP;   // A utilizar con los valores de Stop Loss y Take Profit

Como puedes ver, hemos añadido más parámetros. Antes de seguir hablando sobre los nuevos parámetros, vamos a comentar algo que se ve ya en el código. Las dos barras inclinadas ‘//’ nos permiten poner comentarios en nuestros códigos. Con los comentarios, podemos saber lo que representan nuestras variables, o que estamos haciendo en este instante concreto en nuestro código. Además, nos ofrece una mejor comprensión de nuestro código. Hay dos maneras básicas de escribir comentarios:

// Otros parámetros …

Es un comentario de una sola línea

/*

  Es un comentario de varias líneas

*/

Es un comentario de varias líneas. Los comentarios de varias líneas empiezan con los símbolos /* y se acaban con */.

El compilador ignora todos los comentarios cuando compila tu código.

La utilización de comentarios de una sola línea, para los parámetros de entrada es una buena opción para que nuestros usuarios de EA puedan entender lo que representan estos parámetros. En las propiedades de entrada de EA, nuestros usuarios no podrán ver el parámetro en sí, pero en su lugar, podrán ver los comentarios, tal como se muestran a continuación:

Figura 7. Parámetros de entrada del Expert Advisor

Figura 7. Parámetros de entrada del Expert Advisor

Volvamos a nuestro código...

Hemos decidido añadir más parámetros EA. EA_Magic es el número mágico "magic number" para todos los pedidos de nuestro EA. El valor mínimo de ADX (Adx_Min) se declara como dato de tipo double. El double se utiliza para almacenar constantes de punto flotante, formadas por una parte entera, un punto decimal y una parte fraccionaria.

Ejemplo:

double mysum = 123.5678;

double b7 = 0.09876;

El lote de trading "Lot to trade" (Lot) representa el volumen del instrumento financiero que queremos para el trading. A continuación hemos declarado otros parámetros que utilizaremos:

adxHandle se usará para almacenar el identificador del indicador ADX, mientras maHandle se usará para almacenar el identificador del indicador Moving Average. Los parámetros plsDI[], minDI[], adxVal[] son arrays dinámicos que van a almacenar los valores de +DI, -DI y el ADX principal (del indicador ADX) para cada barra en el gráfico. maVal[] es un array dinámico que almacena los valores del indicador Moving Average para cada barra en el gráfico.

Por cierto, ¿qué son los arrays dinámicos? Un array dinámico es un array declarado sin dimensión. En otras palabras, no se especifica ningún valor entre el par de corchetes para indicar su tamaño. De lo contrario, un array estático, tiene definidas sus dimensiones en la declaración.

Ejemplo:

double allbars[20]; // este va ocupar 20 elementos

p_close es una variable que usaremos para almacenar el Close price para la barra a la que vamos a realizar un seguimiento para la comprobación de nuestras transacciones de Compra/Venta.

STP y TKP son para almacenar los valores del Stop Loss y el Take Profit en nuestro EA.

2.3. SECCIÓN DE INICIALIZACIÓN DEL EA

int OnInit()
  {
//--- Obtener el identificador para el indicador ADX
   adxHandle=iADX(NULL,0,ADX_Period);
//--- Obtener el identificador para el indicador Moving Average
   maHandle=iMA(_Symbol,_Period,MA_Period,0,MODE_EMA,PRICE_CLOSE);
//--- Qué pasa si el identificador devuelve un valor no valido
   if(adxHandle<0 || maHandle<0)
     {
      Alert("Ha ocurrido un error al crear los identificadores de los indicadores -error: ",GetLastError(),"!!");
     }

Obtenemos aquí los identificadores de nuestro indicador, mediante las respectivas funciones de indicadores.

El identificador del indicador ADX se obtiene mediante la función iADX. sus parámetros u argumentos son el símbolo del gráfico (chart symbol)(NULL significa también el símbolo actual en el gráfico actual), el período/intervalo (period/timeframe) del gráfico (0 significa también el período actual en el gráfico actual) y el período del promedio del ADX (ADX averaging period) para calcular el índice (que hemos definido anteriormente en la sección parámetros de entrada).  

int  iADX(
   string           symbol,         // nombre del símbolo
   ENUM_TIMEFRAMES  period,         // período
   int              adx_period      // período del promedio
   );

El identificador del indicador Moving Average se obtiene mediante la función iMA. Tiene los siguientes argumentos:

  • el símbolo del gráfico (que se puede obtener mediante _symbol, symbol() o NULL para el símbolo actual en el gráfico actual),
  • el período/intervalo del gráfico (que se puede obtener mediante _period, period(), o 0 para el período actual en el gráfico actual),
  • el período del promedio de Moving Average (que hemos definido anteriormente en la sección parámetros de entrada),
  • el desplazamiento "shift " del indicador en relación al gráfico del precio (aquí el desplazamiento es 0),
  • El tipo de Suavizado promedio móvil "Moving average smoothing" (puede ser cualquiera de los siguientes métodos de promediación: Simple Averaging-MODE_SMA, Exponential Averaging-MODE_EMA, Smoothed Averaging-MODE_SMMA o Linear-Weighted Averaging-MODE_LWMA), y
  • el precio "price" que se utiliza para el promedio (aquí utilizamos el precio de cierre).

int  iMA(
   string               symbol,            // nombre del símbolo
   ENUM_TIMEFRAMES      period,            // período
   int                  ma_period,         // período del promedio
   int                  ma_shift,          // desplazamiento horizontal
   ENUM_MA_METHOD       ma_method,         // tipo de suavizado
   ENUM_APPLIED_PRICE   applied_price      // tipo de precio o identificador
   );

Consulta el manual de MQL5 para obtener más detalles sobre estas funciones de indicadores. Esto te ayudará a comprender mejor cómo utilizar cada indicador.

Comprobamos otra vez si hay algún error, en el caso de que la función no consiga devolver el identificador, tendremos el error INVALID_HANDLE. Utilizamos la función de aviso para mostrar el error, mediante la función GetlastError.

//--- Vamos a procesar pares de divisas con precios de 5 o 3 dígitos en lugar de 4
   STP = StopLoss;
   TKP = TakeProfit;
   if(_Digits==5 || _Digits==3)
     {
      STP = STP*10;
      TKP = TKP*10;
     }

Hemos decidido almacenar los valores de Stop Loss y Take Profit en las variables STP y TKP que hemos definido previamente. ¿Por qué hacemos esto?

Porque los valores almacenados en los parámetros de ENTRADA "INPUT" son de sólo lectura, no se pueden modificar. Nos tenemos que asegurar ahora de que nuestro EA funciona correctamente con todos los brokers. Digits oDigits() devuelve el número de decimales que definen la precisión del precio actual del símbolo en el gráfico actual. Para un gráfico de precios de 5 o 3 dígitos, multiplicamos tanto el Stop Loss y el Take Profit por 10.

2.4. SECCIÓN DE DESINICIALIZACIÓN DEL EA

 

Puesto que se llama a esta función siempre que se desactiva el EA o se quita de un gráfico, lanzaremos todos los identificadores de indicadores que se crearon durante el proceso de inicialización. Hemos creado dos identificadores, uno para el indicador ADX y el otro para el indicador Moving Average.

Para lograrlo usaremos la función IndicatorRelease(). Sólo necesita un argumento (el identificador del indicador "indicator handle")

bool  IndicatorRelease(
   int       indicator_handle,     // identificador del indicador
   );

La función elimina el identificador del indicador y lanza el bloque de cálculo del indicador, si no se ha utilizado.

2.5 LA SECCIÓN ONTICK DEL EA

Lo primero que tenemos que hacer, es comprobar si tenemos bastantes barras en nuestro gráfico. Podemos encontrar el número de barras en el historial de cualquier gráfico, mediante la función Bars. Requiere dos parámetros, el símbolo (se puede obtener mediante _Symbol o Symbol(). Estos dos parámetros devuelven el símbolo actual para el gráfico actual en el cual está nuestro EA) y el período o intervalo del gráfico actual (se puede obtener mediante Period o Period(). Estos dos devuelven el período del gráfico actual de nuestro EA).

Si el número de barras disponibles es inferior a 60, paramos nuestro EA, ya que tenemos bastantes barras en el gráfico.  La función Alert muestra un mensaje en una ventana separada. Toma cualquier valor separado por coma, como parámetros/argumentos. En este caso, tenemos solamente una cadena. La inicialización de nuestro EA deja de funcionar según el resultado.

//+------------------------------------------------------------------+
//| Función Expert tick                                              |
//+------------------------------------------------------------------+
void OnTick()
  {
// ¿Tenemos bastantes barras para trabajar con ellas?
   if(Bars(_Symbol,_Period)<60) // si el número de barras es inferior a 60 barras 
     {
      Alert("Tenemos menos de 60 barras, EA va a salir ahora!!");
      return;
     }
// Usaremos la variable estática Old_Time para el tiempo de barra.
// A cada ejecución de OnTick compararemos el tiempo de barra actual con el que está guardado.
// Si el tiempo de barra no es igual al tiempo guardado, significa que tenemos un nuevo tick.
   static datetime Old_Time;
   datetime New_Time[1];
   bool IsNewBar=false;

// copiando el último tiempo de barra al elemento New_Time[0]
   int copied=CopyTime(_Symbol,_Period,0,1,New_Time);
   if(copied>0) // de acuerdo, se han copiado los datos con éxito
     {
      if(Old_Time!=New_Time[0]) // si el tiempo anterior no es igual al nuevo tiempo de barra
        {
         IsNewBar=true;   // si no es la primera llamada, la nueva barra ha aparecido
         if(MQL5InfoInteger(MQL5_DEBUGGING)) Print("Tenemos una nueva barra aquí",New_Time[0]," el tiempo anterior era ",Old_Time);
         Old_Time=New_Time[0];            // guardando el tiempo de barra
        }
     }
   else
     {
      Alert("Ha ocurrido un error al copiar los datos del historial de los tiempos, error =",GetLastError());
      ResetLastError();
      return;
     }

//--- EA debe comprobar si hay una nueva operación de trading, sólo si hay una barra nueva
   if(IsNewBar==false)
     {
      return;
     }
 
//--- ¿Tenemos bastantes barras para trabajar con ellas?
   int Mybars=Bars(_Symbol,_Period);
   if(Mybars<60) // si el número de barras es inferior a 60 barras
     {
      Alert("Tenemos menos de 60 barras, EA va a salir ahora!!");
      return;
     }

//--- Define algunas estructuras MQL5 que usaremos en nuestro trading
   MqlTick latest_price;     // Se utiliza para obtener las recientes/últimas cotizaciones de precios
   MqlTradeRequest mrequest;  // Se utiliza para enviar nuestras peticiones de trading
   MqlTradeResult mresult;    // Se utiliza para obtener los resultados de nuestro trading
   MqlRates mrate[];         // Se utiliza para almacenar los precios, los volúmenes y el diferencial de cada barra
   ZeroMemory(mrequest);  // Inicialización de la estructura mrequest

El Expert Advisor realizará las operaciones de trading al inicio de cada barra nueva, por lo que es necesario resolver el problema con la identificación de una nueva barra. En otras palabras, queremos asegurarnos de que el EA no va estar comprobando las posiciones Largas/Cortas "Long/Short" en cada tick, queremos que el EA compruebe las posiciones Largas/Cortas sólo cuando haya una barra nueva. 

Empezamos declarando una variable estática de fecha y hora Old_Time, que va almacenar el tiempo de barra. La hemos declarado estática, porque queremos que se guarde el valor en la memoria, hasta la siguiente llamada de la función OnTick. Entonces, podremos comparar sus valores con la variable New_Time variable (además del tipo de fecha y hora), que es un array de un elemento para almacenar el nuevo (reciente) tiempo de barra. Declaramos también una variable IsNewBar de tipo booleano y definimos su valor como false. Se hace así, porque queremos que tenga el valor TRUE solamente cuando tenemos una barra nueva.

Utilizamos la función CopyTime para obtener el tiempo de la barra actual. Está función copia el tiempo de barra al array New_Time de un elemento; y si hay éxito, se compara el tiempo de la nueva barra con en el tiempo de la barra anterior. Si los tiempos no son iguales, significa que tenemos una barra nueva, y establecemos la variable IsNewBar a TRUE y guardamos el valor del tiempo de barra actual en la variable Old_Time.

La variable IsNewBar indica que tenemos una barra nueva. Si su valor es FALSE, terminamos la ejecución de la función OnTick.  

Echa un vistazo a este código

if(MQL5InfoInteger(MQL5_DEBUGGING)) Print("Tenemos una barra nueva aquí ",New_Time[0]," el tiempo anterior era ",Old_Time);

Comprueba la ejecución del modo de depuración y mostrará el mensaje acerca de los tiempos de barras durante el modo de depuración, lo abordaremos más detenidamente.

Lo siguiente que queremos hacer, es comprobar si tenemos bastantes barras para trabajar con ellas. ¿Porqué hacerlo de nuevo? Sólo queremos estar seguros de que nuestro EA funciona correctamente. Cabe señalar que, aunque se llame a la función OnInit sólo una vez cuando está vinculada a un gráfico, se llama a la función OnTick cada vez que haya un nuevo tick (cotización de precio).

Como puedes ver, lo hemos hecho otra vez aquí, pero de manera distinta. Decidimos almacenar el número total de barras en el historial que obtenemos de la expresión

int Mybars=Bars(_Symbol,_Period);

en una nueva variable, Mybars, declarada dentro de la función OnTick. Es un variable de tipo local, a diferencia de la variable que hemos declarado en la sección de los PARÁMETROS DE ENTRADA de nuestro código. Mientras las variables declaradas en la sección Parámetros de entrada de nuestro código, están disponibles para todas las funciones que las puedan necesitar, dentro de nuestro código, las variables declaradas dentro de una única función son limitadas y están disponibles sólo para esa función. No se pueden utilizar fuera de la misma.

A continuación, hemos declarado algunas variables de tipos de estructuras MQL5, que se utilizarán en esta sección de nuestro EA. MQL5 tiene un buen número de estructuras hechas, lo que simplifica bastante las cosas a los desarrolladores de EA. Vamos a ver las estructuras, una tras otra.

MqlTick

Esta es la estructura utilizada para almacenar los últimos precios de los símbolos.

struct MqlTick
  {
   datetime     time;          // Momento de la última actualización de precio
   double       bid;           // Precio de Venta "Bid" actual
   double       ask;           // Precio de Compra "Ask" actual
   double       last;          // Precio de la última transacción (Last)
   ulong        volume;        // Volumen del precio "Last" actual
  };

Cualquier variable que se declara del tipo MqlTick se puede fácilmente utilizar para obtener los valores actuales de Ask, Bid, Last yVolume una vez hayas llamado la función SymbolInfoTick().

Así que declaramos latest_price como tipo MqlTick para que podamos utilizarla en la obtención de los precios de Compra "Ask" y Venta "Bid".

MqlTradeRequest

Se utiliza esta estructura para llevar a cabo todas las peticiones de trading para una operación de trading. En su estructura, contiene todos los campos necesarios para realizar una transacción de trading.

struct MqlTradeRequest
  {
   ENUM_TRADE_REQUEST_ACTIONS    action;       // Tipo de operación de trading
   ulong                         magic;        // ID del Expert Advisor (magic number)
   ulong                         order;        // Ticket de orden
   string                        symbol;       // Símbolo de la operación de trading
   double                        volume;       // El volumen solicitado en lotes
   double                        price;        // Precio
   double                        stoplimit;    // nivel StopLimit de la orden
   double                        sl;           // Nivel Stop Loss de la orden
   double                        tp;           // Nivel Take Profit de la orden
   ulong                         deviation;    // Desviación máxima aceptable del precio de venta
   ENUM_ORDER_TYPE               type;          // Tipo de orden
   ENUM_ORDER_TYPE_FILLING       type_filling;  // Tipo de ejecución de orden
   ENUM_ORDER_TYPE_TIME          type_time;     // Momento de ejecución de la orden
   datetime                      expiration;    // Momento de expiración de la orden (para ordenes del tipo ORDER_TIME_SPECIFIED)
   string                        comment;       // Comentario de la orden
  };

Cualquier variable que se declara del tipo MqlTradeRequest puede ser utilizada para enviar órdenes a nuestras operaciones de trading. Aquí, hemos declarado mrequest del tipo MqlTradeRequest.

MqlTradeResult

Se devuelve el resultado de cualquier operación de trading como una estructura predefinida MqlTradeResult de tipo especial. Cualquier variable que se declara del tipo MqlTradeResult podrá acceder a los resultados de la petición de trading.

struct MqlTradeResult
  {
   uint     retcode;          // Código del resultado de la operación
   ulong    deal;             // Ticket de la transacción, si se hace
   ulong    order;            // Ticket de la orden, si se coloca
   double   volume;           // Volumen de transacciones, confirmado por el broker
   double   price;            // Precio de la transacción, confirmado por el broker
   double   bid;              // Precio Venta "Bid" actual
   double   ask;              // Precio de Compra "Ask" actual
   string   comment;          // Comentario del broker de la operación (por defecto, se pone la descripción de la operación)
  };

Aquí, hemos declarado mresult del tipo MqlTradeResult.

MqlRates

Se almacena el precio (Open, Close, High, Low), el tiempo, los volúmenes de cada barra y el diferencial de un símbolo en esta estructura.  Se puede utilizar cualquier array declarado del tipo MqlRates para almacenar el precio, los volúmenes y el historial de los diferenciales de un símbolo.

struct MqlRates
  {
   datetime time;         // Inicio del período
   double   open;         // Precio de apertura
   double   high;         // El precio más alto del período
   double   low;          // El Precio más bajo del período
   double   close;        // Precio de cierre
   long     tick_volume;  // Volumen del tick
   int      spread;       // Diferencial
   long     real_volume;  // Volumen de trading
  };

Aquí, hemos declarado un array mrate[]  que servirá para almacenar esta información.

/*
     Vamos a asegurarnos de que los valores de nuestros arrays para las Tasas "Rates", valores de ADX y valores de MA se almacenan con la misma indexación que los arrays de las series de tiempo
*/
// los arrays de tasas
   ArraySetAsSeries(mrate,true);
// el array de los valores de ADX DI+
   ArraySetAsSeries(plsDI,true);
// el array de los valores de ADX DI-
   ArraySetAsSeries(minDI,true);
// los arrays de los valores de ADX
   ArraySetAsSeries(adxVal,true);
// los arrays de los valores de MA-8
   ArraySetAsSeries(maVal,true);

A continuación, decidimos establecer los arrays que vamos a utilizar para almacenar los detalles de las barras en series. Esto nos garantiza que los valores que se van a copiar en los array se van a indexar igual que las series de tiempo, es decir, 0, 1, 2, 3, para coincidir con los índices de las barras. Esto se hace mediante l función ArraySetAsSeries().

bool  ArraySetAsSeries(
   
void  array[],     // array por referencia
   bool  set          // true indica inversión del orden de indexación
   );

Cabe señalar que esto también se puede hacer sólo una vez, en la sección de inicialización de nuestro código. Sin embargo, y para simplificar la explicación, he decidido mostrarlo en este punto.

//--- Obtener la última cotización de precio mediante la estructura MQL5 MqlTick
   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("Ha ocurrido un error al obtener la última cotización de precio -error:",GetLastError(),"!!");
      return;
     }

Utilizamos ahora la función SymbolInfoTick para obtener la última cotización de precio. Esta función tiene dos argumentos – el símbolo del gráfico y la variable de estructura MqlTick (latest_price). Un vez más, si hay un error, lo reportamos.

//--- Obtener los detalles de las últimas 3 barras
   if(CopyRates(_Symbol,_Period,0,3,mrate)<0)
     {
      Alert("Ha ocurrido un error al copiar los datos tasas/historial -error:",GetLastError(),"!!");
      return;
     }

A continuación, copiamos la información sobre las últimas tres barras en nuestro array tipo Mqlrates mediante la función CopyRates. La función CopyRates sirve para obtener el historial de los datos con estructura MqlRates de un determinado Símbolo-Período para una cantidad determinada en el array tipo MqlRates. 

int  CopyRates(
   string           symbol_name,       // nombre del símbolo
   ENUM_TIMEFRAMES  timeframe,         // período
   int              start_pos,         // posición de inicio
   int              count,             // número de datos que se desea copiar
   MqlRates         rates_array[]      // array donde se copia
   );

El nombre del símbolo se obtiene mediante ‘_symbol’, y el período/intervalo se obtiene mediante ‘_period’. Para la posición e inicio, empezaremos desde la barra actual, Bar 0 y contaremos sólo tres barras, Barras 0, 1, y 2. El resultado se almacena en nuestro array mrate[].

El array mrate[] contiene ahora toda la información sobre el precio, tiempo, volúmenes y diferencial de las barras 0 , 1 y 2.  Por lo tanto, para obtener los detalles de cualquier barra, utilizaremos lo siguiente:

mrate[bar_number].bar_property

Por ejemplo, podemos conseguir la siguiente información sobre cada barra:

mrate[1].time   // Tiempo de inicio Barra 1
mrate[1].open   // Precio de apertura Barra 1
mrate[0].high   // Precio máximo barra 0 (barra actual), etc.

Luego, se copian todos los valores del indicador en los arrays dinámicos que hemos declarado mediante la función CopyBuffer.

int  CopyBuffer(
   int       indicator_handle,     // identificador del indicador 
   int       buffer_num,           // número de buffer del indicador
   int       start_pos,            // posición de inicio
   int       count,                // cantidad a copiar
   double    buffer[]              // array donde se copiarán los datos
   );

El identificador del indicador es el identificador que hemos creado en la sección OnInit. En cuanto al número de buffers, el indicador ADX tiene tres (3) buffers:

  • 0 - MAIN_LINE,
  • 1 - PLUSDI_LINE,
  • 2 - MINUSDI_LINE.

El indicador Moving Average tiene sólo un (1) indicador:

  • 0 – MAIN_LINE.

Copiamos desde la barra actual (0) hasta las dos últimas barras. De esta manera, el número total de barras a copiar es 3 (barras 0,1 y 2). buffer[] es el array dinámico de destino que habíamos declarado anteriormente – adxVal, plsDI, minDI y maVal.

Como puedes observar de nuevo aquí, intentamos capturar cualquier error que pueda ocurrir durante el proceso de copia. Si hay un error, no hace falta seguir.

Es importante señalar que las funciones CopyBuffer() y CopyRates() devuelven el número total de los registros copiados con éxito, y devuelven -1 en caso de error. Es por eso que estamos comprobando los valores inferiores a 0 (cero), en las siguientes funciones de comprobación de error.

//--- Copiar los nuevos valores de nuestros indicadores a los buffers (arrays) mediante el identificador
   if(CopyBuffer(adxHandle,0,0,3,adxVal)<0 || CopyBuffer(adxHandle,1,0,3,plsDI)<0
      || CopyBuffer(adxHandle,2,0,3,minDI)<0)
     {
      Alert("Ha ocurrido un error al copiar los buffers del indicador ADX -error:",GetLastError(),"!!");
      return;
     }
   if(CopyBuffer(maHandle,0,0,3,maVal)<0)
     {
      Alert("Ha ocurrido un error al copiar el buffer del indicador Moving Average indicator -error:",GetLastError());
      return;
     }

En este momento queremos comprobar si ya tenemos una posición de Compra "Buy" o Venta "Sell" abierta, en otras palabras, queremos asegurarnos de que tenemos UNA sola operación de trading de Compra o Venta abierta a la vez. No queremos abrir una nueva Compra, si ya tenemos una abierta, y tampoco queremos abrir una nueva Venta, si ya tenemos una abierta.

Para conseguirlo tenemos, antes que nada, que declarar dos variables de tipo booleano (Buy_opened y Sell_opened) que van a tomar el valor TRUE si ya tenemos una posición abierta, que sea de Compra o Venta.

//--- no tenemos errores, así que continuamos
//--- ¿Tenemos posiciones abiertas ya?
    bool Buy_opened=false;  // variable que almacena el resultado de la posición abierta Compra
    bool Sell_opened=false; // variable que almacena el resultado de la posición abierta Venta
    
    if (PositionSelect(_Symbol) ==true)  // tenemos una posición abierta
    {
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            Buy_opened = true;  // Es una Compra
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            Sell_opened = true; // Es una Venta
         }
    }

Para saber si tenemos una posición abierta, utilizamos la función PositionSelect. Esta función devuelve TRUE si ya tenemos una posición abierta y FALSE si no hay ninguna.

bool  PositionSelect(
   string  symbol      // Nombre del Símbolo
 );

Y tiene como principal argumento/parámetro, el símbolo (par de divisas) que queremos comprobar. Aquí, utilizamos _symbol, puesto que estamos comprobando el símbolo actual (par de divisas).

Si la expresión devuelve TRUE, entonces queremos comprobar si la posición abierta es de Compra o Venta. Para ello, utilizamos la función PositionGetInteger, que nos devuelve el tipo de posición abierta al utilizarla con el modificador POSITION_TYPE. Nos devuelve el Identificador del tipo de posición que puede ser POSITION_TYPE_BUY o POSITION_TYPE_SELL

long  PositionGetInteger(
   ENUM_POSITION_PROPERTY  property_id      // Identificador de propiedad
   );

En nuestro caso, lo utilizamos para averiguar cuál de las posiciones está ya abierta. Si es una Venta, almacenamos el valor TRUE en Sell_opened y si es una Compra, almacenamos el valor TRUE en Buy_opened. Vamos a poder utilizar estas dos variables más adelante, cuando estaremos haciendo las comprobaciones de las condiciones de Venta o Compra en nuestro código.

Ahora es el momento de almacenar el precio de cierre correspondiente a la barra que vamos a utilizar en nuestra configuración de Compra/Venta. Recuerda que anteriormente hemos declarado una variable para eso.

// Copiar el precio de cierre de la barra anterior a la barra actual, que es la Barra 1

   p_close=mrate[1].close;  // precio de cierre de la barra 1

Hecho esto, pasamos a la siguiente etapa.

/*
    1. Configuración de búsqueda de Compra (long/Buy): MA-8 creciendo, 
    el precio de cierre anterior lo supera, ADX > 22, +DI > -DI
*/
//--- Declarar variables de tipo booleano para almacenar las condiciones de nuestra Compra
   bool Buy_Condition_1 = (maVal[0]>maVal[1]) && (maVal[1]>maVal[2]); // MA-8 creciendo
   bool Buy_Condition_2 = (p_close > maVal[1]);         // el precio de cierre anterior superior a MA-8
   bool Buy_Condition_3 = (adxVal[0]>Adx_Min);          // Valor actual de ADX superior al valor mínimo (22)
   bool Buy_Condition_4 = (plsDI[0]>minDI[0]);          // +DI superior a -DI

//--- Poniendo todo junto   
   if(Buy_Condition_1 && Buy_Condition_2)
     {
      if(Buy_Condition_3 && Buy_Condition_4)
        {
         // ¿alguna posición de Compra abierta?
         if (Buy_opened) 
         {
            Alert("Ya tenemos una posición de Compra abierta!!!"); 
            return;    // No abras una nueva posición de compra
         }
         mrequest.action = TRADE_ACTION_DEAL;                                // ejecución inmediata de la orden 
         mrequest.price = NormalizeDouble(latest_price.ask,_Digits);          // último precio de compra
         mrequest.sl = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss
         mrequest.tp = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take Profit
         mrequest.symbol = _Symbol;                                         // par de divisas
         mrequest.volume = Lot;                                            // número de lotes de la operación
         mrequest.magic = EA_Magic;                                        // Magic Number de la orden
         mrequest.type = ORDER_TYPE_BUY;                                     // Orden de compra
         mrequest.type_filling = ORDER_FILLING_FOK;                          // Tipo de ejecución de la orden
         mrequest.deviation=100;                                            // Desviación en relación al precio actual
         //--- enviar orden
         OrderSend(mrequest,mresult);

Ahora es el momento de empezar a comprobar las condiciones de Compra.

Analicemos la expresión anterior, ya que representa la estrategia que hemos diseñado anteriormente. Estamos declarando una variable del tipo booleano para cada una las condiciones que se deben cumplir antes de colocar una orden. Una variable del tipo booleano sólo puede tener los valores TRUE o FALSE. Por lo tanto, nuestra estrategia de Compra se ha dividido en cuatro condiciones.  Si alguna de las condiciones se cumple, se almacena el valor TRUE en nuestra variable booleana, de lo contrario, se almacena el valor FALSE. Vamos a verlas una por una.

bool Buy_Condition_1 = (maVal[0]>maVal[1]) && (maVal[1]>maVal[2]);

Aquí estamos comprobando los valores de MA-8 en las Barras 0, 1 y 2. Si el valor de MA-8 en la barra actual es superior a su valor en la barra anterior Barra 1 y si también el valor de MA-8 en la Barra 1 es superior a su valor en la Barra 2, significa que MA-8 está creciendo. Esto satisface una de nuestras condiciones de Compra.

bool Buy_Condition_2 = (p_close > maVal[1]); 

Esta expresión comprueba si la precio de cierre de la Barra 1 es mayor que el valor de MA-8 en el mismo período (período de la Barra 1). Si el precio es mayor, eso quiere decir que nuestra segunda condición también se ha cumplido, así que podemos comprobar las otras condiciones. Si no se cumplen las dos condiciones que acabamos de describir, ya no hace falta comprobar las otras condiciones. Es por ello que hemos decidido incluir las siguientes expresiones en estas dos condiciones iniciales (expresiones).

bool Buy_Condition_3 = (adxVal[0]>Adx_Min);

Ahora queremos comprobar si el valor actual de ADX (el valor de ADX en la Barra 0) es superior al valor mínimo de ADX que hemos declarado en los parámetros de entrada. Si esta condición se cumple, es decir, que el valor de ADX es superior al valor mínimo necesario; queremos también asegurarnos de que el valor plusDI es superior al valor de minusDI. Es lo que hacemos en la siguiente expresión

bool Buy_Condition_4 = (plsDI[0]>minDI[0]);

Si se cumplen todas estas condiciones, es decir, devuelven el valor TRUE, entonces queremos asegurarnos de no abrir ninguna posición de Compra nueva si ya tenemos una abierta. Ahora es el momento de comprobar el valor de la variable Buy_opened que hemos declarado anteriormente en nuestro código.

// ¿alguna posición de Compra abierta?
if (Buy_opened) 
   {
      Alert("Ya tenemos una posición de Compra abierta!!!"); 
      return;    // No abras una nueva posición de compra
   }

Si Buy_opened es TRUE, no queremos abrir otra posición de Compra, por lo que, se muestra un aviso para informarnos y luego volvemos para que nuestro EA espere el próximo tick. Sin embargo, si Buy_opened es FALSE, preparamos nuestros registros mediante la variable MqlTradeRequest del tipo (mrequest) que hemos declarado anteriormente para enviar nuestra orden.

  • La acción aquí, que es el tipo de la operación de trading, es TRADE_ACTION_DEAL ya que estamos colocando una orden de trading para una ejecución inmediata. Así que, Para modificar una orden, usaremos TRADE_ACTION_MODIFY. Para borrar una orden, usaremos TRADE_ACTION_REMOVE.  Para obtener el último precio de Compra (Ask), usaremos nuestro latest_price de tipo MqlTick.  El precio Stop loss de la orden se obtiene restando nuestro StopLoss en puntos del precio de Compra, mientras se obtiene el precio take profit de la orden sumando nuestro TakeProfit en puntos al precio de Compra. También puedes observar que hemos utilizado la función NormalizeDouble para los valores del precio de Compra, StopLoss y TakeProfit, es una buena práctica para redondear siempre estos precios al número de dígitos del par de divisas antes de enviarlos al servidor de trading. 
  • symbol  es el símbolo actual _Symbol o Symbol()). type es el tipo de orden que estamos colocando, aquí estamos colocando una orden de Compra ORDER_TYPE_BUY. Para una orden de Venta, sería ORDER_TYPE_SELL.
  • type_filling es el tipo de ejecución de la orden; ORDER_FILLING_FOK significa que la transacción se debe ejecutar únicamente para un volumen determinado y a un precio igual o mejor que el precio indicado en la orden. Si no hay volumen suficiente de ofertas en el símbolo de la orden, no se ejecutará la orden.

La función OrderSend() tiene dos argumentos, la variable de tipo MqlTradeRequest y la variable de tipo MqlTradeResult.

bool  OrderSend(
   MqlTradeRequest&  request      // estructura de la petición
   MqlTradeResult&   result       // estructura de la respuesta
   );

Como puedes observar, hemos utilizado nuestra variable de tipo MqlTradeRequest y la variable de tipo MqlTradeResult para la colocación de nuestra orden mediante OrderSend.

         // obtener el código resultante
         if(mresult.retcode==10009 || mresult.retcode==10008) //Petición completada u orden colocada
           {
            Alert("Se ha colocado con éxito una orden de Compra con Ticket#:",mresult.order,"!!");
           }
         else
           {
            Alert("No se ha podido completar la orden de Compra -error:",GetLastError());
            ResetLastError();           
            return;
           }

Después de enviar nuestra orden, vamos a utilizar ahora la variable de tipo MqlTradeResult para comprobar el resultado de nuestra orden. Si se ejecuta nuestra orden con éxito, queremos saberlo, y si no se ejecuta, queremos saberlo también. Con la variable ‘mresult’ de tipo MqlTradeResult podemos acceder al return code de la Operación y también al número de ticket de la orden "order ticket number" si se coloca una orden.

El código de retorno 10009 significa que la petición OrderSend fue llevada a cabo con éxito, mientras 10008 significa que nuestra orden se ha colocado. Por eso hemos comprobado ambos códigos de retorno. Si obtenemos cualquiera de los dos, estamos seguros de que nuestra orden se ha completado o se ha colocado.

Para comprobar una oportunidad de Venta, hacemos las comprobaciones de manera opuesta a lo que hicimos para la oportunidad de Compra, excepto para nuestro ADX que debe ser superior al valor Mínimo indicado.

/*
    2. Comprobación de la condición de Venta (Short/Sell): MA-8 decreciendo, 
    el precio de cierre anterior inferior a él, ADX > 22, -DI > +DI
*/
//--- Declarar variables de tipo booleano para almacenar las condiciones de nuestra Venta
   bool Sell_Condition_1 = (maVal[0]<maVal[1]) && (maVal[1]<maVal[2]);  // MA-8 decreciendo
   bool Sell_Condition_2 = (p_close <maVal[1]);                         // el precio de cierre anterior inferior a MA-8
   bool Sell_Condition_3 = (adxVal[0]>Adx_Min);                         // Valor actual de ADX superior al mínimo (22)
   bool Sell_Condition_4 = (plsDI[0]<minDI[0]);                         // +DI superior a -DI
   
 //--- Poniendo todo junto
   if(Sell_Condition_1 && Sell_Condition_2)
       {
         if(Sell_Condition_3 && Sell_Condition_4)
           {
            // ¿alguna posición de Venta abierta?
            if (Sell_opened) 
            {
                Alert("Ya tenemos una posición de Venta abierta!!!"); 
                return;    // No abras una nueva posición de Venta
            }
            mrequest.action = TRADE_ACTION_DEAL;                                 // ejecución inmediata de la orden
            mrequest.price = NormalizeDouble(latest_price.bid,_Digits);          // último precio de Venta
            mrequest.sl = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss
            mrequest.tp = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit
            mrequest.symbol = _Symbol;                                         // par de divisas
            mrequest.volume = Lot;                                            // número de lotes de la operación
            mrequest.magic = EA_Magic;                                        // Magic Number de la orden
            mrequest.type= ORDER_TYPE_SELL;                                     // Orden de Venta
            mrequest.type_filling = ORDER_FILLING_FOK;                          // Tipo de ejecución de la orden
            mrequest.deviation=100;                                           // Desviación en relación al precio actual
            //--- enviar orden
            OrderSend(mrequest,mresult);

Igual que hicimos en la sección de Compra, estamos declarando una variable del tipo booleano para cada una las condiciones que se deben cumplir antes de colocar una orden. Una variable del tipo booleano sólo puede tener los valores TRUE o FALSE. Por lo tanto, nuestra estrategia de Venta se ha dividido en cuatro condiciones.  Si alguna de las condiciones se cumple, se almacena el valor TRUE en nuestra variable booleana, de lo contrario, se almacena el valor FALSE. Vamos a verlas una por una, como hicimos en la sección Compra

   bool Sell_Condition_1 = (maVal[0]<maVal[1]) && (maVal[1]<maVal[2]);

Aquí estamos comprobando los valores de MA-8 en las Barras 0, 1 y 2. Si el valor de MA-8 en la barra actual es inferior a su valor en la barra anterior Barra 1 y si también el valor MA-8 de la Barra 1 es inferior a su valor en la Barra 2, significa que MA-8 está decreciendo. Esto satisface una de nuestras condiciones de Venta.

   bool Sell_Condition_2 = (p_close <maVal[1]); 

Esta expresión comprueba si el precio de cierre de la Barra 1 es inferior al valor de MA-8 en el mismo período (período de la Barra 1). Si el precio es inferior, eso quiere decir que nuestra segunda condición también se ha cumplido, así que podemos comprobar las otras condiciones. Si no se cumplen las dos condiciones que acabamos de describir, ya no hace falta comprobar las otras condiciones. Es por ello que hemos decidido incluir las siguientes expresiones en estas dos condiciones iniciales (expresiones).

   bool Sell_Condition_3 = (adxVal[0]>Adx_Min); 

Ahora queremos comprobar si el valor actual de ADX (el valor de ADX en la Barra 0) es superior al valor mínimo de ADX que hemos declarado en los parámetros de entrada. Si esta condición se cumple, es decir, que el valor de ADX es superior al valor mínimo necesario; queremos también asegurarnos de que el valor MinusDI es superior al valor de plusDI. Es lo que hacemos en la siguiente expresión

bool Sell_Condition_4 = (plsDI[0]<minDI[0]);

Si se cumplen todas estas condiciones, es decir, devuelven el valor TRUE, entonces queremos asegurarnos de no abrir ninguna posición de Venta nueva si ya tenemos una abierta. Ahora es el momento de comprobar el valor de la variable Buy_opened que hemos declarado anteriormente en nuestro código.

// ¿alguna posición de Venta abierta?
            if (Sell_opened) 
            {
                Alert("Ya tenemos una posición de Venta abierta!!!"); 
                return;    // No abras una nueva posición de Venta
            }

Si Sell_opened es TRUE, no queremos abrir otra posición de Venta, por lo que, se muestra un aviso para informarnos y luego volvemos para que nuestro EA espere el próximo tick. Sin embargo, si Sell_opened es FALSE, configuramos nuestra operación de Venta, del mismo modo que lo hicimos en la operación de compra.

La principal diferencia reside en la manera de calcular nuestro precio Stop Loss y Take Profit. Además, mientras estamos vendiendo, vendemos todo al precio de Venta "Bid"; es por lo que utilizamos nuestra variable latest_price del tipo MqlTick para obtener el último precio de Venta. Hay otro tipo de orden, que hemos comentado antes, es ORDER_TYPE_SELL.

También, hemos utilizado la función NormalizeDouble para los valores del precio de Venta, StopLoss y TakeProfit, es una buena práctica para redondear siempre estos precios al número de dígitos del par de divisas antes de enviarlos al servidor de trading. 

Igual que hicimos con la orden de Compra, también debemos comprobar si nuestra orden de Venta se ha realizado con éxito o no. Así que usamos la misma expresión que en nuestra orden de Compra.

         if(mresult.retcode==10009 || mresult.retcode==10008) //Petición completada u orden colocada
           {
            Alert("Se ha colocado con éxito una orden de Venta con Ticket#:",mresult.order,"!!");
           }
         else
           {
            Alert("No se ha podido completar la orden de Venta -error:",GetLastError());
            ResetLastError();
            return;
           }
        }


3. Depuración y pruebas de nuestro Expert Advisor

En este punto, necesitamos probar nuestro EA, para saber si nuestra estrategia funciona o no. También es posible que haya un error o dos en el código de nuestro EA. Veremos esto en la siguiente etapa.

3.1 LA DEPURACIÓN

La depuración de nuestro código nos permite ver su rendimiento línea por línea (si establecemos unos puntos de interrupción "breakpoints") y si observamos algunos errores o fallos en algunos puntos de nuestro código, efectuamos las correcciones necesarias rápidamente, antes de utilizar nuestro código en operaciones de trading en condiciones reales.

Vamos a ver paso a paso el proceso de depuración de nuestro Expert Advisor, primero, estableciendo los puntos de interrupción y después, sin puntos de interrupción. Para ello, asegúrate de que no has cerrado el Editor. En primer lugar, vamos a elegir la gráfica que queremos utilizar para probar nuestro EA. En la barra del menú del Editor, haz clic en Herramientas y luego en Opciones como se muestra a continuación:

Figura 8. Ajustes de las opciones de depuración

Figura 8. Ajustes de las opciones de depuración 

Una vez que aparezca la ventana de las Opciones, selecciona el par de divisas y el período/intervalo a utilizar y haz clic en Aceptar:

Figura 9. Ventana de las opciones del depurador

Antes de iniciar el depurador, vamos a definir los puntos de interrupción. Los Puntos de interrupción nos permiten controlar el comportamiento/rendimiento de nuestro código en unas líneas o puntos determinados. En lugar de recorrer el código de una vez, lo que hace el depurador es detenerse en cada punto de interrupción, esperando que hagas algo. Esto nos permite analizar nuestro código y controlar su comportamiento en cada uno de los puntos de interrupción que hemos definido. Podemos también evaluar los valores de algunas de nuestras variables para ver si todo está funcionando según lo previsto.

Para insertar un punto de interrupción, hay que situarse en la línea de código donde queremos fijar el punto. Hacer un doble clic izquierdo en el campo gris situado en el borde izquierdo de línea de código, verás un pequeño botón azul redondo con un cuadro blanco en su interior. Otra manera de hacerlo, es situar el cursor en cualquier parte de la línea en la que queremos colocar un punto de interrupción y pulsar F9. Para eliminar el punto de interrupción, haz doble clic sobre él o pulsa F9 otra vez.

Figura 10. Colocación de un punto de interrupción

Figura 10. Colocación de un punto de interrupción

Vamos a definir los puntos de interrupción en cinco líneas distintas de nuestro código. 

Para simplificar la explicación, los vamos a etiquetar del 1 al 5.

Para continuar, establece los puntos de interrupción en las 5 líneas de código, como se muestra en la siguiente figura. El punto de interrupción 1 es el que habíamos creado más arriba.

Figura 11. Colocación de puntos de interrupción adicionales

Figura 11. Colocación de puntos de interrupción adicionales

Ahora que hemos acabado de establecer de los puntos de referencia, estamos ya preparados para iniciar el proceso de depuración de nuestro código.

Para iniciar el depurador, pulsa F5 o haz clic en el botón verde de la barra de herramientas del MetaEditor:

 Figura 12. Iniciando el depurador

Figura 12. Iniciando el depurador

Lo primero que hace el editor es compilar el código, si detecta errores, los mostrará y si no hay ninguno, te indicará que el código se ha compilado con éxito.

Figura 13. Informe de compilación

Figura 13. Informe de compilación

Ten en cuenta que el hecho de que el código se haya compilado con éxito no significa que está exento de errores. Dependiendo de cómo se ha escrito tu código, puede haber errores de tiempo de ejecución. Por ejemplo, si cualquiera de nuestras expresiones no evalúa correctamente debido a una control escaso, el código se compilará correctamente, pero puede que no funcione correctamente. Ahora pasamos a la acción...

Un vez se haya completado la depuración del código, te va a dirigir al terminal de trading, y vincular el EA al gráfico que has señalado en los ajustes de las opciones de MetaEditor. Al mismo tiempo, te muestra la parte de los parámetros de Entrada del EA. Puesto que no estamos configurando nada, le damos un clic a Aceptar.

Figura 14. Parámetros de entrada del Expert Advisor para la compilación

Figura 14. Parámetros de entrada del Expert Advisor para la compilación

Ahora verás claramente nuestro EA en la esquina superior izquierda del gráfico.

Una vez empieza OnTick(), se detendrá nada más llegar al punto de interrupción 1.

Figura 15. El depurador se detiene en el primer punto de interrupción.

Figura 15. El depurador se detiene en el primer punto de interrupción.

Verás una flecha verde en esta la línea de código. Esto te indica que la línea de código anterior se ha ejecutado; ahora estamos preparados para ejecutar la línea actual.

Antes de continuar, voy a dar algunas explicaciones. Si te fijas en la barra de herramientas del Editor, te darás cuenta de que los tres botones con flechas curvas que antes eran grises, se han habilitado ahora. Esto se debe a que estamos ejecutando el depurador. Estos botones/comandos sirven para ir paso a paso por el código (Paso con entrada "Step into", Paso sin entrada "Step over" o Paso fuera "Step out").

Figura 16. Comando Paso con entrada

Figura 16. Comando Paso con entrada

Se utiliza el Paso con entrada para pasar de una etapa de la ejecución del programa a la siguiente etapa, entrando a las funciones a las que se llama en la línea de código. Para ejecutar este comando, haz clic en el botón o pulsa F11. (Usaremos este comando para la depuración Paso a paso de nuestro código).

Figura 17. Comando Paso sin entrada

Figura 17. Comando Paso sin entrada

Por otra parte, Paso sin entrada no entra en las funciones a las que se llama en esta línea de código. Para ejecutar este comando, haz clic en el botón o pulsa F10.

Figura 18. Comando Paso fuera

Figura 18. Comando Paso fuera

Haz clic en el botón o pulsa Shift+F11 para ejecutar una etapa del programa que está en un nivel superior.

También verás en la parte baja del Editor la ventana Caja de Herramientas. La pestaña Depurar de esta ventana tiene las siguientes columnas:

  • Archivo: Muestra el nombre del archivo al que se ha llamado.
  • Función: Muestra la función actual del archivo al que se ha llamado.
  • Línea: Muestra el número de la línea de código a partir de la cual se ha llamado a la función en el archivo.
  • Expresión: En esta columna, se puede especificar el nombre de cualquier expresión/variable a la que quieres controlar a lo largo de nuestro código. 
  • Valor: Muestra el valor de la expresión/variable que hemos introducido en la columna Expresión.
  • Tipo: Muestra el tipo de dato de la expresión/variable de la columna Expresión.

Volvamos al proceso de depuración...

Lo siguiente que queremos hacer ahora es introducir las variables/expresiones que queremos controlar de nuestro código. Asegúrate de controlar solamente las variables/expresiones que son realmente importantes. Por ejemplo, vamos a controlar lo siguiente:

  • Old_Time (tiempo de la barra anterior)
  • New_Time[0] (tiempo de la barra actual)
  • IsNewBar (señal o "flag" que indica una barra nueva)
  • Mybars (total de barras en el historial) –Nuestro EA lo utiliza

Puedes añadir más variables, como los valores de ADX, los valores de MA-8, etc.

Para añadir una expresión/variable, haz doble clic debajo del título Expresión o un clic derecho en la columna Expresión y selecciona Add , como se indica en la figura de abajo.

Introduce la expresión/variable a controlar u observar.

Figura 19. Ventana de observación de las expresiones

Figura 19. Ventana de observación de las expresiones

Introduce todas las variables/expresiones necesarias...

Figura 20. Añadir las expresiones o variables a observar

Figura 20. Añadir las expresiones o variables a observar

Si aún no se ha declarado la variable, su tipo es "Unknown identifier" (excepto las variables estáticas).

Vamos a seguir...

Figura 21. Comando Paso con entrada en acción

Figura 21. Comando Paso con entrada en acción

Haz un clic en Paso con entrada o pulsa el botón F11  y observa lo que ocurre. Mantén pulsado este botón o F11 hasta llegar al punto de interrupción nº 2, continúa hasta llegar al punto de interrupción nº 4 como se muestra a continuación y observa la ventana de observación de expresiones.


Figura 22. Observación de las expresiones o variables

Figura 22. Observación de las expresiones o variables

Figura 23. Observación de las expresiones o variables

Figura 23. Observación de las expresiones o variables

Figura 24. Observación de las expresiones o variables

Figura 24. Observación de las expresiones o variables

Una vez que haya un nuevo tick, se vuelve a la primera línea de código de la función OnTick(). Y se van a reiniciar todos los valores de nuestras variables/expresiones, debido a este nuevo tick, excepto las variables estáticas. En este caso, tenemos una variable estática Old_Time.

Figura 25. Valores de las variables cuando hay un evento NewTick

Figura 25. Valores de las variables cuando hay un evento NewTick

Para volver otra vez al proceso, continúa pulsando la tecla  F11 y sigue observando las variables en la ventana de observación de expresiones. Puedes detener el depurador y a continuación quitar todos los puntos de interrupción.

Como podrás ver, en el modo de depuración aparece el mensaje "Tenemos una nueva barra aquí...".

Figura 26. El Expert Advisor muestra el mensaje en el modo de depuración

Figura 26. El Expert Advisor muestra el mensaje en el modo de depuración

Ejecuta el proceso de depuración de nuevo; pero esta vez sin puntos de interrupción. Sigue observando cada tick y comprobando si se cumple alguna de nuestras condiciones de Compra/Venta, estas colocaran una operación de trading, y puesto que hemos escrito el código de manera que nos informe si se ha colocado una orden con éxito o no, veremos una aviso.

Figura 27. El Expert Advisor coloca una operación de trading durante la depuración

Figura 27. El Expert Advisor coloca una operación de trading durante la depuración

Creo que se puede dejar el EA funcionando unos minutos más, mientras te tomas un café. Una vez hayas vuelto y que hayas ganado un poco de dinero (es broma), haz clic en el botón Detener la depuración (Rojo) del MetaEditor para detener la depuración.

Figura 28. Deteniendo el depurador

Figura 28. Deteniendo el depurador

Lo que acabamos de hacer realmente aquí es ver que nuestro EA sólo comprueba si hay una operación de trading en la apertura de una nueva barra y que nuestro EA funciona realmente. Todavía hay mucho margen para los ajustes del código de nuestro EA.

Que quede claro que en este punto el Terminal de trading debe estar conectado a Internet, de lo contrario, no funcionará la depuración al no estar en marcha el terminal.

3.2 PROBANDO LA ESTRATEGIA DE NUESTRO EA

Ahora queremos probar nuestro EA mediante el Simulador de Estrategias integrado en el Terminal de trading.  Para ejecutar el Simulador de Estrategias, pulsa CONTROL+R o haz un clic en el menú Ver de la barra de menú del Terminal y luego haz clic en el Simulador de Estrategias, como se muestra a continuación.

Figura 26. Ejecución del Simulador de Estrategias

Figura 26. Ejecución del Simulador de Estrategias

El Simulador (Simulador de Estrategias) aparece en la parte inferior del terminal. Para poder ver todos los ajustes del Simulador, hay que expandir/reajustar la ventana. Para ello, mueve el puntero del ratón hasta la línea señalada con la flecha roja (como se muestra a continuación).

Figura 27. La ventana del Simulador de Estrategias

Figura 27. La ventana del Simulador de Estrategias

El puntero del ratón se convierte en una flecha de doble punta, mantén pulsado el ratón y arrastra la línea hacia arriba. Continúa hasta que puedas ver todos los parámetros de la pestaña de configuración. 

Figura 28. Pestaña de configuración del Simulador de Estrategias

Figura 28. Pestaña de configuración del Simulador de Estrategias

  1. Selecciona el EA que quieres probar
  2. Selecciona el par de divisas a utilizar en la prueba
  3. Selecciona el Período/Intervalo a utilizar en la prueba
  4. Selecciona Período Personalizado y establece las fechas
  5. Define las fechas del período personalizado para la prueba
  6. Ejecución en modo Normal
  7. Selecciona la cantidad del depósito a utilizar en la prueba en USD
  8. Pon Optimización en Deshabilitado (no estamos optimizando ahora, sólo queremos probar)
  9. Haz clic en este botón cuando estás listo para ejecutar la prueba.

Antes de pulsar el botón Empezar, vamos a ver las otras pestañas del Simulador.

La pestaña Agentes

El procesador utilizado por el Simulador para la prueba. Dependerá del tipo de procesador de tu ordenador. El mío es un procesador de un (1) sólo núcleo.

Figura 29. La Pestaña Agentes del Simulador de Estrategias

Figura 29. La Pestaña Agentes del Simulador de Estrategias

Para un solo agente, verás algo parecido a la siguiente figura:

Figura 30. La Pestaña Agentes del Simulador de Estrategias durante una prueba

Figura 30. La Pestaña de Agentes del Simulador de Estrategias durante una prueba

La pestaña Diario

Es aquí donde se muestran todas las actividades durante la prueba.

Figura 31. La pestaña Diario mostrando las actividades de las operaciones

Figura 31. La pestaña Diario mostrando las actividades de las operaciones

La pestaña Parámetros de entrada

En esta pestaña se indican los parámetros de entrada del EA.

Figura 32. La Pestaña Parámetros de entrada del Simulador de Estrategias

Figura 32. La Pestaña Parámetros de entrada del Simulador de Estrategias

Si queremos optimizar nuestro EA, tendemos que definir los valores de los parámetros del área rodeada.

  • Empezar indica los valores iniciales de nuestro Simulador.
  • Paso es la tasa de incremento del valor elegido, y
  • Parar es el valor final del Simulador.

Sin embargo, en nuestro caso no estamos optimizando nuestro EA, por lo que no vamos a tocar estos parámetros por ahora. 

Una vez todo configurado, volvemos a la pestaña Configuración y hacemos clic en el botón Empezar. A continuación, se inicia el Simulador. Todo lo que tienes que hacer ahora, es tomar otra taza de café, si quieres, o, si eres como yo, querrás observar todo el proceso, entonces dirígete a la pestaña Diario

La pestaña Gráfico

A medida que vayas observando los mensajes en la Pestaña Diario, verás una nueva pestaña, llamada Gráfico, y que acaba de ser generada. Cuando cambias a la pestaña Gráfico, verás como aumenta o disminuye el gráfico en función de los resultados de tus operaciones.

Figura 33. El resultado gráfico del la prueba del Expert Advisor

Figura 33. El resultado gráfico del la prueba del Expert Advisor

La pestaña Resultados

Una vez finalizada la prueba, verás otra pestaña, llamada Resultados. Al cambiar a la pestaña Resultados, verás el informe de la prueba.

 Figura 34. La pestaña Resultados del Simulador de Estrategias, mostrando el informe de la prueba

 Figura 34. La pestaña Resultados del Simulador de Estrategias, mostrando el informe de la prueba

Puedes ver el Beneficio bruto (Gross Profit), el Beneficio neto (Net Profit), el Total de transacciones, el total de transacciones no rentables y mucho más. Es muy interesante observar que tenemos unos 1 450,00 USD en el período que hemos elegido para nuestra prueba. Por lo menos, tenemos algo de beneficio.

Quiero dejar algo muy claro aquí. Descubrirás que los ajustes de los parámetros del EA que ves en el Simulador de Estrategias son distintos a los ajustes iniciales de los parámetros de entrada del EA. Acabo de demostrar que puedes cambiar cualquiera de estos parámetros de entrada para aprovechar mejor de tu EA. En lugar de utilizar un período de 8 para el Moving Average y el  ADX, lo he cambiado a 10 para el Moving Average y a 14 para el ADX. He cambiado también el Stop Loss de 30 a 35. Por último, pero no menos importante, he decido utilizar un intervalo de 2 horas. Y recuerda, este es el Simulador de Estrategias.

Si quieres ver un informe completo de la prueba, haz un clic derecho en cualquier sitio de la pestaña Resultados, aparecerá un menú. Desde este menú, selecciona ‘Guardar como Informe.

Figura 35. Almacenamiento de los resultados de la prueba

Figura 35. Almacenamiento de los resultados de la prueba

Aparecerá la ventana de diálogo de Guardar, escribe un nombre para tu informe (si quieres, sino deja el nombre por defecto) y haz clic en el botón Guardar. Se guardará el informe completo en formato HTML.

Para ver el gráfico de la prueba realizada, haz clic en Abrir Gráfico y se mostrará el gráfico.

Figura 36. Gráfico de los resultados de la prueba 

Figura 36. Gráfico de los resultados de la prueba

Eso es todo, hemos escrito y probado con éxito nuestro EA y ahora tenemos un resultado con el cual podemos trabajar. Puedes volver a la pestaña de Configuración del Simulador de Estrategias y hacer la prueba con otros intervalos de tiempo/períodos.

Tarea

Quiero que lleves a cabo pruebas con diferentes pares de divisas, diferentes períodos, diferentes Stop Loss, diferentes Take Profit y comprobar el rendimiento del EA. Puedes incluso probar otros valores de Moving Average y ADX. Como he dicho antes, está es la esencia del Simulador de Estrategias. Me gustaría también que compartáis vuestros resultados conmigo.

Conclusión

En esta guía paso a paso, hemos podido ver cuáles son los pasos básicos para escribir un Expert Advisor sencillo, basándonos en una elaborada estrategia de trading. También hemos visto cómo se hace la comprobación de errores de nuestro EA mediante el depurador. Además, hemos explicado la forma de probar el rendimiento de nuestro EA mediante el Simulador de Estrategias. Con esto, hemos podido ver el potencial y la solidez del nuevo lenguaje MQL5. Nuestro EA aún no es perfecto ni completo, ya que todavía se le deben aplicar muchas mejoras antes de poder utilizarlo en condiciones reales.

Aún queda mucho por aprender y te recomiendo leer el artículo de nuevo, junto al manual de MQL5, y probar todo lo que has aprendido en este artículo, y te aseguro que podrás escribir Expert Advisors en un futuro no muy lejano.

Feliz codificación.


Traducción del inglés realizada por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/en/articles/100

Archivos adjuntos |
my_first_ea.mq5 (11.73 KB)
Creación de indicadores de tick en MQL5 Creación de indicadores de tick en MQL5

En este artículo vamos a ver la creación de dos indicadores: el indicador de tick, que representa el gráfico de tick del precio, y el indicador de vela de tick, que representa las velas con el número de ticks especificados. Cada uno de los indicadores escribe los precios de llegada en un archivo y utiliza los datos guardados tras el reinicio del indicador (estos datos pueden ser usados también por los demás programas).

Los estilos de representación en MQL5 Los estilos de representación en MQL5

Hay 6 estilos de representación en MQL4 y 18 en MQL5. Por ello, merece la pena dedicar un artículo a una introducción sobre los estilos de representación en MQL5. En este artículo vamos a tratar en profundidad los estilos de representación en MQL5. Además, crearemos un indicador para mostrar estos estilos y configurar el trazado.

MQL5: Crea tu propio indicador MQL5: Crea tu propio indicador

¿Qué es un indicador? Se trata de un conjunto de valores calculados y que queremos que se muestren en la pantalla de manera cómoda para nosotros. Los conjuntos de valores se representan en los programas en forma de matrices. De este modo, la creación del indicador consiste en escribir un algoritmo que maneja algunas matrices (matrices de precios) y graba los resultados del procesamiento de otras matrices (valores del indicador). Mediante la descripción de la creación de True Strength Index (Índice de fuerza verdadera), el autor muestra cómo escribir indicadores en MQL5.

Aplicar un Indicador a Otro Aplicar un Indicador a Otro

Al escribir un indicador que usa la forma corta de la llamada de función OnCalculate(), puede que no se dé cuenta del hecho de que un indicador se puede calcular no solo por datos de precio, sino también por datos de otro indicador (independientemente de si viene incorporado o es personalizado). ¿Desea mejorar un indicador para su correcta aplicación a los datos del otro indicador? En este artículo, revisaremos todos los pasos para realizar tal modificación.