Una breve guía de inicio rápido para principiantes

Dmitriy Parfenovich | 27 diciembre, 2013

Introducción

¡Hola, apreciado lector! En este artículo intentaré explicarle y mostrarle cómo puede dominar, de forma fácil y rápida, los principios necesarios para crear asesores expertos, trabajar con indicadores, etc. Está destinado a principiantes y no se utilizarán ejemplos difíciles o complejos. Por tanto, el artículo puede que no sea muy inspirador o informativo para aquellos que ya saben cómo programar asesores expertos.

El asesor experto y su estructura

Un asesor experto es un programa escrito en lenguaje MQL que especifica las condiciones para realizar la transacción o mantenerse al margen.

Básicamente, la estructura de un asesor experto puede estar formada por un gran número de bloques, pero para que sea más fácil entenderlo voy a realizar un ejemplo muy sencillo generado por defecto en MetaEditor.

Todo el asesor experto puede dividirse visualmente en 4 partes, cada una de las cuales es responsable de una cierta parte del trabajo a realizar.

Los principales bloques de EA 
Figura 1. Los principales bloques de un asesor experto

  1. El bloque prámetros (block of parameters) contiene información para el terminal, permitiendo a este gestionar el asesor experto de forma adecuada. Los parámetros más habituales son la versión de asesor experto, el nombre del fabricante y una breve descripción.

  2. El bloque OnInit() obtiene el control una vez que el asesor experto es cargado en el terminal. Puede contener varios datos relacionados con la inicialización del asesor experto (declarando variables y matrices, obteniendo controladores de indicador, etc.). Es decir, este bloque no tiene ninguna función directamente asociada con el trading.

  3. El bloque OnDeinit() actúa de forma contraria al bloque OnInit(). Es llamada cuando el asesor experto completa su operación (apagado del asesor experto/terminal o inicialización sin éxito de un asesor experto). Una de las principales funciones de este bloque es la revocación de la asignación previa de memoria ocupada por el asesor experto cuando ya no es necesario. En otras palabras, describe los procesos de borrado de variables, matrices y controladores de indicador, etc.

  4. El bloque OnTick() es llamado cada vez que se recibe nueva información en el símbolo (par de divisas) desde el servidor. Especifica condiciones para realizar las transacciones y funciones de trading.

Ejemplo de un nuevo documento generado por defecto en MetaEditor
   Figura 2. Ejemplo de un nuevo documento generado por defecto en MetaEditor


Permítanme explicarlo usando el ejemplo anterior. Tenemos un código de un asesor experto "vacío", un tipo de plantilla de asesor experto que va a necesitar ser rellenada posteriormente.

Lo que podemos ver aquí es lo siguiente:

Como dije antes, la estructura puede ser mucho más compleja y estar compuesta de una gran cantidad de bloques, al contrario de lo que ocurre con este sencillo ejemplo. Cuando crea que esto no es suficiente, puede añadir sus propios bloques,
 

Indicadores y cómo gestionarlos

Los indicadores son pequeños programas escritos en MQL que se muestran en el gráfico del precio o en una ventana separada bajo el gráfico del precio, y nos permiten realizar análisis técnicos del mercado.

Todos los indicadores pueden clasificarse en dos tipos: indicadores que siguen una tendencia y osciladores.

Los indicadores que siguen una tendencia son, por regla general, dibujados en el gráfico del precio y se usan para identificar la dirección de la tendencia, mientras que los osciladores se suelen mostrar bajo el gráfico del precio y sirven para identificar los puntos de entrada.

La mayoría de los indicadores tienen al menos un buffer (indicador buffer) que contiene sus datos de lectura en un momento determinado. Al igual que los asesores expertos, el indicador tiene su símbolo y período de tiempo en el que es calculado.

El buffer del indicador puede considerarse como una cola cuyo último elemento es un valor en ejecución.

Ejemplo del indicador media móvil
   Figura 3. Ejemplo del indicador media móvil

El buffer de indicador es una matriz donde el primer elemento (con índice 0) lleva datos en la vela más a la derecha y el siguiente elemento (con índice 1) lleva datos en la segunda vela a la derecha, etc. Tal disposición de elementos se llama series de tiempo.

Observe el ejemplo siguiente:

Asumamos que el par de divisas que tenemos es EUR/USD, el período de tiempo es 1 hora.
En primer lugar necesitamos añadir el indicador al asesor experto y obtener su controlador.

Handle es un puntero único para el indicador que nos permite utilizar ese indicador en cualquier lugar del programa.

int iMA_handle; 
iMA_handle=iMA("EURUSD",PERIOD_H1,10,0,MODE_SMA,PRICE_CLOSE);
Examinémoslo más detenidamente,

La primera línea define una variable que va a almacenar el controlador del indicador. La segunda línea llama al indicador (llamado aquí el indicador de media móvil), especifica sus parámetros y guarda el controlador en la variable para usarlo en el futuro.
Tras teclear "iMA(" en MetaEditor, aparecerá un mensaje de ayuda sobre esa línea mostrando los parámetros de llamada separados por comas.

Ejemplo del mensaje de ayuda para los parámetros del indicador media móvil
   Figura 4. Ejemplo del mensaje de ayuda para los parámetros del indicador media móvil

Podemos ver los siguientes parámetros listados de izquierda a derecha:

  1. el nombre del símbolo (aparece en negrita en el mensaje de ayuda) es un parámetro de texto de par de divisas (símbolo);
  2. período de tiempo;
  3. período del indicador (llamado aquí el período promedio);
  4. desplazamiento del gráfico en N barras hacia adelante/atrás. Un número positivo indica el desplazamiento del gráfico en N barras hacia adelante, mientras que un número negativo indica un desplazamiento del gráfico en N barras hacia atrás;
  5. método de promediación;
  6. precio aplicado o un controlador de un indicador diferente.

Hay un único conjunto de variables y sus correspondientes tipos para cada indicador. Si se encuentra con un indicador desconocido, puede siempre encontrar información sobre el mismo en la ayuda contextual. Por ejemplo, una vez que ha tecleado iMA y presionado F1, se abrirá una ventana de ayuda proporcionando información sobre ese indicador específico y una descripción detallada de todas sus propiedades.

Ejemplo de llamada a la ventana de ayuda para la descripción del indicador presionando F1
   Figura 5. Ejemplo de llamada a la ventana de ayuda para la descripción del indicador presionando F1

Después de escribir el código e iniciar el asesor experto en el terminal, veremos (una vez que aparece el asesor experto en la esquina superior derecha del gráfico de precio) que el indicador no se encuentra en el gráfico. Esto no es un error, estaba previsto. Para que aparezca necesitamos añadir otra línea:

ChartIndicatorAdd(ChartID(),0,iMA_handle);

Veamos ahora cómo funciona. Pase el cursor sobre el comando ChartIndicatorAdd y presione F1 para leer la información de ayuda sobre la finalidad del comando. Puede leerse que este comando:

Añade un indicador con el controlador especificado en la ventana de gráfico indicada.

El segundo parámetro, que es igual a cero, es el número de subventana. Las subventanas contienen, normalmente, osciladores bajo el gráfico del precio. ¿Se acuerda? Puede haber muchos. Para mostrar el indicador en la subventana, solo necesita especificar el número de subventana de forma que sea mayor que el número existente en 1, es decir, el número que sigue al último existente.

Habiendo cambiado el código de la siguiente forma:

ChartIndicatorAdd(ChartID(),1,iMA_handle);

nuestro indicador aparecerá en la subventana bajo el gráfico del precio.

Ahora es el momento de intentar conseguir algunos datos del indicador. Para ello, vamos a declarar una matriz dinámica, realizaremos un indexado de la matriz como series de tiempo por comodidad y copiaremos los valores del indicador en esta matriz.

double iMA_buf[];
ArraySetAsSeries(iMA_buf,true);
CopyBuffer(iMA_handle,0,0,3,iMA_buf);

El ejemplo anterior muestra que hemos declarado la matriz dinámica iMA_buf[] de tipo doble, ya que el indicador de media móvil se basa en precios y los precios tienen números fraccionarios.

La siguiente línea establece el indexado para la matriz, de forma que los elementos con menores índices almacenan valores más antiguos, mientras que los elementos con mayores índices almacenan valores más recientes. Este se utiliza por comodidad para evitar confusiones, ya que los buffers de indicador en todos los indicadores son indexados como series de tiempo.

La última línea sirve para copiar los valores del indicador en la matriz iMA_buf[]. Estos datos se encuentran ahora disponibles para ser usados. 

 

Órdenes, transacciones y posiciones

Comencemos con las órdenes.

Las órdenes de mercado representan instrucciones para vender o comprar una cierta cantidad de instrumentos financieros al precio actual de mercado.
Las órdenes pendientes representan instrucciones para ejecutar la transacción sujetas a ciertas condiciones. Las órdenes pendientes tienen un cierto período de caducidad que, una vez cumplido, supone su borrado.

Para dejarlo más claro, permítanme ilustrarlo con un ejemplo: abrimos una posición larga de 1 lote, es decir, cursamos una orden al precio actual de mercado (por ejemplo) y del tamaño de 1 lote. Si la solicitud es válida será enviada al servidor para ser procesada. Tan pronto como se complete su procesamiento, aparecerá una posición en la pestaña "Transacción" del terminal. Supongamos que decidimos abrir otra posición larga de tamaño 1 lote. Siguiendo el procesamiento de la orden, no veremos dos órdenes en la pestaña "Tab" sino una posición de tamaño igual a 2 lotes. Es decir, la posición es el resultado de la ejecución de un número de órdenes.

Vamos ahora a realizar un ejercicio práctico. Los siguientes campos de la estructura requieren ser rellenados para realizar una solicitud:

struct MqlTradeRequest
{
ENUM_TRADE_REQUEST_ACTIONS action; // Type of action
ulong magic; // Expert Advisor ID (magic number)
ulong order; // Order ticket
string symbol; // Trade instrument
double volume; // Requested trade size in lots
double price; // Price 
double stoplimit; // StopLimit level of the order
double sl; // Stop Loss level of the order
double tp; // Take Profit level of the order
ulong deviation; // Maximum allowed deviation from the requested price
ENUM_ORDER_TYPE type; // Order type
ENUM_ORDER_TYPE_FILLING type_filling; // Order type by execution
ENUM_ORDER_TYPE_TIME type_time; // Order type by duration
datetime expiration; // Order expiration time (for orders of the ORDER_TIME_SPECIFIED type)
string comment; // Comment to the order
};

Al haber varias órdenes, cada una de ellas tiene su propio conjunto de parámetros obligatorios. No voy a extenderme demasiado en este punto. El sitio web proporciona una gran cantidad de información al respecto. Si no se especifica o se incluye incorrectamente alguno de los parámetros obligatorios para un determinado tipo de orden, la solicitud será fallida.

Hemos visto aquí la estructura anterior para poder mostrar la dificultad que conlleva construirla.

Stop Loss y Take Profit

Stop Loss y Take Profit son órdenes especiales que se cursan como "medida de seguridad". Es decir, en caso de errores o de posiciones abiertas por un asesor experto y que muestran una pérdida, la orden Stop Limit puede limitar las pérdidas hasta un cierto nivel predefinido.

La orden Take Profit funciona de forma similar aunque, en este caso, solo limita el beneficio. Puede ser de ayuda para evitar tener que preocuparse por cerrar una posición. Se cerrará cuando se alcance un cierto nivel de precio. En otras palabras, estas órdenes representan nuestro "plan de seguros" en caso de que el mercado se vuelva en nuestra contra o queramos retirar beneficios.

Este tipo de órdenes no pueden ser cursadas por sí solas de forma independiente, ya que solo pueden modificar posiciones ya existentes.

Usar librerías estándar

Hemos llegado por fin a la librería estándar. Esta librería viene incluida en el terminal, de ahí el nombre "librería estándar". Comprende funciones que facilitan la programación de asesores expertos y llevar a cabo procesos complejos parcialmente, como por ejemplo la generación de solicitudes de transacciones.

Las solicitudes de transacciones (véase también clases de transacciones) se encuentran en el siguiente directorio: Include\Trade\, y pueden añadirse usando #include.

Ejemplo:

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

Las clases anteriores pueden considerarse básicas, ya que la mayoría de asesores expertos pueden ser programados usando solo estas dos clases (librerías). Las llamo librerías:


Algunas veces puede ser útil usar otra librería:
#include <Trade\OrderInfo.mqh>
Contiene funciones para trabajar con órdenes en caso de que, digamos, nuestra estrategia requiera el uso de órdenes pendientes.

¿Recuerda la estructura de la solicitud de transacción repleta de parámetros que requiere un cierto conocimiento para ser usada?

A continuación les mostraré un ejemplo de solicitud de transacción realizada usando una librería:

CTrade m_Trade;
m_Trade.Sell(lot,symbol_name,price,sl,tp,comment);

Aquí hay un total de 6 parámetros, donde solo uno de ellos es obligatorio (el tamaño de la orden, que es el primer parámetro).
Voy a especificar a continuación cada uno de ellos:

Hay varias formas de cerrar una posición:

  1. cerrar la posición completamente
    CPositionInfo m_Position;
    m_Position.Select(symbol_name);
    m_Trade.PositionClose(symbol_name);
  2. para cerrar la posición cursando una orden inversa del mismo tamaño
    CTrade m_Trade;
    m_Trade.Buy(lot,symbol_name,price,sl,tp,comment);
  3. usando un método más complejo por el que se busca entre todas las posiciones abiertas aquella que cumple los parámetros requeridos (símbolo, tipo, número mágico, identificador de posición, etc.) para ser cerrada.
    No voy a dar ningún ejemplo de lo anterior ya que resultaría de cierta dificultad para los principiantes.

Reuniéndolo todo

Es hora de poner en práctica los conocimientos adquiridos con un asesor experto.

//+------------------------------------------------------------------+
//|                                           fast-start-example.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>                                         //include the library for execution of trades
#include <Trade\PositionInfo.mqh>                                  //include the library for obtaining information on positions

int               iMA_handle;                              //variable for storing the indicator handle
double            iMA_buf[];                               //dynamic array for storing indicator values
double            Close_buf[];                             //dynamic array for storing the closing price of each bar

string            my_symbol;                               //variable for storing the symbol
ENUM_TIMEFRAMES   my_timeframe;                             //variable for storing the time frame

CTrade            m_Trade;                                 //structure for execution of trades
CPositionInfo     m_Position;                              //structure for obtaining information of positions
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   my_symbol=Symbol();                                      //save the current chart symbol for further operation of the EA on this very symbol
   my_timeframe=PERIOD_CURRENT;                              //save the current time frame of the chart for further operation of the EA on this very time frame
   iMA_handle=iMA(my_symbol,my_timeframe,40,0,MODE_SMA,PRICE_CLOSE);  //apply the indicator and get its handle
   if(iMA_handle==INVALID_HANDLE)                            //check the availability of the indicator handle
   {
      Print("Failed to get the indicator handle");              //if the handle is not obtained, print the relevant error message into the log file
      return(-1);                                           //complete handling the error
   }
   ChartIndicatorAdd(ChartID(),0,iMA_handle);                  //add the indicator to the price chart
   ArraySetAsSeries(iMA_buf,true);                            //set iMA_buf array indexing as time series
   ArraySetAsSeries(Close_buf,true);                          //set Close_buf array indexing as time series
   return(0);                                               //return 0, initialization complete
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   IndicatorRelease(iMA_handle);                             //deletes the indicator handle and deallocates the memory space it occupies
   ArrayFree(iMA_buf);                                      //free the dynamic array iMA_buf of data
   ArrayFree(Close_buf);                                    //free the dynamic array Close_buf of data
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   int err1=0;                                             //variable for storing the results of working with the indicator buffer
   int err2=0;                                             //variable for storing the results of working with the price chart
   
   err1=CopyBuffer(iMA_handle,0,1,2,iMA_buf);               //copy data from the indicator array into the dynamic array iMA_buf for further work with them
   err2=CopyClose(my_symbol,my_timeframe,1,2,Close_buf);    //copy the price chart data into the dynamic array Close_buf for further work with them
   if(err1<0 || err2<0)                                    //in case of errors
   {
      Print("Failed to copy data from the indicator buffer or price chart buffer");  //then print the relevant error message into the log file
      return;                                                               //and exit the function
   }

   if(iMA_buf[1]>Close_buf[1] && iMA_buf[0]<Close_buf[0])   //if the indicator values were greater than the closing price and became smaller
     {
      if(m_Position.Select(my_symbol))                     //if the position for this symbol already exists
        {
         if(m_Position.PositionType()==POSITION_TYPE_SELL) m_Trade.PositionClose(my_symbol);  //and this is a Sell position, then close it
         if(m_Position.PositionType()==POSITION_TYPE_BUY) return;                              //or else, if this is a Buy position, then exit
        }
      m_Trade.Buy(0.1,my_symbol);                          //if we got here, it means there is no position; then we open it
     }
   if(iMA_buf[1]<Close_buf[1] && iMA_buf[0]>Close_buf[0])  //if the indicator values were less than the closing price and became greater
     {
      if(m_Position.Select(my_symbol))                     //if the position for this symbol already exists
        {
         if(m_Position.PositionType()==POSITION_TYPE_BUY) m_Trade.PositionClose(my_symbol);   //and this is a Buy position, then close it
         if(m_Position.PositionType()==POSITION_TYPE_SELL) return;                             //or else, if this is a Sell position, then exit
        }
      m_Trade.Sell(0.1,my_symbol);                         //if we got here, it means there is no position; then we open it
     }
  }
//+------------------------------------------------------------------+

Vamos a probar nuestro asesor experto con los parámetros de la siguiente forma:

Como usamos los valores del indicador y los precios de cierre comenzando con la primera barra (la barra cero es una barra actual y activa), el gráfico no se volverá a representar. Esto significa que podemos usar el modo de transacción "solo precios de apertura". No afectará a la calidad de la prueba pero hará que se ejecute más rápido.

Y estos son los resultados de las pruebas usando los datos históricos. 

Nuestros resultados de las pruebas del Expert Advisor
   Figura 6. Nuestros resultados de las pruebas del asesor experto

Las reducciones no pueden pasar inadvertidas. Sin embargo, este artículo no tiene por objetivo programar un "super asesor experto" que pueda tener un gran potencial de generar beneficios con una mínima reducción sino, más bien, mostrar lo fácil que puede ser elaborar un asesor experto cuando disponemos de unos conocimientos básicos.
Tenemos un asesor experto de menos de un centenar de líneas de código.
 

Conclusión

Este artículo cubre los principios fundamentales que deben considerarse a la hora de programar un asesor experto. Hemos aprendido cómo usar la ayuda contextual de MetaEditor 5 para obtener información de varias funciones, la idea general de los conceptos de órdenes y posiciones, y finalmente hemos abordado el uso de las librerías estándar.