English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Introducción a MQL5: Cómo escribir un Expert Advisor y un Indicador Personalizado

Introducción a MQL5: Cómo escribir un Expert Advisor y un Indicador Personalizado

MetaTrader 5Ejemplos | 16 diciembre 2013, 10:49
24 412 0
Denis Zyatkevich
Denis Zyatkevich

Introducción

MetaQuotes Programming Language 5 (MQL5), incluido en el terminal del cliente de MetaTrader 5, tiene muchas nuevas posibilidades y un mayor rendimiento, en comparación con MQL4. Este artículo le ayudará a familiarizarse con este nuevo lenguaje de programación. En este artículo se encuentran los sencillos ejemplos de cómo escribir un Expert Advisor y un Indicador Personalizado. También tendremos en cuenta algunos detalles del lenguaje MQL5, que son necesarios para entender estos ejemplos.

Los detalles del artículo y la descripción completa de MQL5 pueden encontrarse en Referencia MQL5, que se incluye en MetaTrader 5. La información contenida en la ayuda incorporada de MQL5 es suficiente para estudiar el lenguaje. Este artículo puede ser útil para aquellos que están familiarizados con MQL4, y también para los principiantes que empiezan a programar sistemas de trading e indicadores.


Primeros pasos con MQL5

La plataforma de trading MetaTrader 5 le permite realizar análisis técnicos de instrumentos financieros y comerciar, tanto en modo manual como automático. Diferencias de MetaTrade 5 con respecto a su predecesor - MetaTrade 4. Particularmente, los conceptos de acuerdo, posición y pedido se han perfeccionado.

  • Posición - es un compromiso de mercado, el número de contratos de instrumentos financieros comprados o vendidos.
  • Pedido - es un pedido para comprar o vender cierta cantidad de instrumentos financieros bajo ciertas condiciones.
  • Acuerdo - es un hecho de ejecución de algún pedido de broker, que conduce a la apertura, la modificación o el cierre de la posición.

El terminal del cliente tiene lenguaje de programación incorporado MQL5, el cual le permite escribir varios tipos de programas con diferentes fines:

  • Expert Advisor - es un programa que comercia de acuerdo con algún algoritmo especificado. Un Expert Advisor le permite implementar el sistema comercial para trading automatizado (las operaciones de trading pueden realizarse sin un agente). Un Expert Advisor puede realizar operaciones comerciales, abrir y cerrar posiciones y controlar pedidos pendientes.
  • Indicador - es un programa que le permite presentar datos en forma gráfica, lo cual es conveniente para el análisis.
  • Script - es un programa que permite realizar alguna secuencia de operaciones de una vez.

Asesores expertos, Indicadores y Scripts pueden activar las funciones de la biblioteca estándar MQL5 y las funciones DLL, incluyendo las bibliotecas del sistema de operación. Los fragmentos de código que se encuentran en los otros archivos, se pueden incluir en el texto del programa, escrito en MQL5.

Para escribir un programa (Expert Advisor, Indicador o Script), puede iniciar el terminal del cliente de MetaTrader 5 y seleccionar Editor de lenguaje de MetaQuotes del menú Herramientas, o simplemente pulse la tecla F4.

Ilustración 1. Iniciar MetaEditor.

En la ventana de MetaEditor 5, seleccione Archivo > Nuevo, o pulse Ctrl+N.

Ilustración 2. Crear nuevo programa.

En la ventana de MQL5, seleccione el tipo de programa que desea crear: 

Ilustración 3. MQL5 Wizard.

En el siguiente paso puede especificar el nombre del programa, información sobre el autor y los parámetros. Esta información será solicitada al usuario después de iniciar el programa.

Ilustración 4. Propiedades generales de Expert Advisor.

Después de esto, se creará la plantilla del programa (Expert Advisor, Indicador o Script), la cual podrá editar y rellenar con su código:

Ilustración 5. Plantilla de un nuevo programa.

Cuando el programa está listo, es necesario compilarlo. Para compilar el programa, seleccione Archivo > Compilar, o pulse la tecla F7:

Ilustración 6. Compilación de programas.

Si no hay errores en el código del programa, se creará el archivo con extensión .ex5. Después de esto, podrá adjuntar este nuevo Expert Advisor, Indicador o Script a la tabla del terminal del cliente de MetaTrader 5 para su ejecución.

Un programa MQL5 es una secuencia de operadores. Cada operador termina con punto y coma ";". Para su comodidad, puede añadir comentarios a su código, los cuales se colocan entre los símbolos "/*" y "*/", o tras "//" al final de la línea. MQL5 es un lenguaje de programación "orientado para eventos". Esto significa que cuando suceden ciertos eventos (lanzamiento o terminación de un programa, llegada de nueva cotización, etc), el terminal del cliente inicia la función correspondiente (subprograma), escrita por el usuario, que realiza las operaciones especificadas. El terminal del cliente tiene los siguientes eventos predefinidos:

  • Inicio sucede cuando se ejecuta Script (usado sólo en Script). Conduce a la ejecución de la función OnStart. Equivalente MQL4 - función Inicio en Scripts.
  • Init sucede cuando se inicia Expert Advisor o Indicador. Conduce a la ejecución de la función OnInit. Equivalente MQL4 - Función Init.
  • Deinit sucede cuando se termina Expert Advisor o Indicador (por ejemplo, tras separar de la tabla, cierre del terminal del cliente, etc.). Conduce a la ejecución de la función OnDeinit. Equivalente MQL4 - Función Deinit.
  • NewTick sucede cuando llega una nueva cotización para el instrumento financiero actual (sólo se usa en Asesores Expertos). Conduce a la ejecución de la función OnTick. Equivalente MQL4 - Función Inicio en Asesores Expertos.
  • Calcular sucede cuando se inicia Indicador (tras la ejecución de la función OnInit) y cuando llega una nueva cotización para el instrumento financiero actual (sólo se usa en Indicadores). Conduce a la ejecución de la función OnCalculate. Equivalente MQL4 - Función Inicio en Indicadores
  • Trade sucede cuando se ejecuta, se modifica o se elimina el pedido, cuando se abre, se modifica o se cierra la posición (sólo se usa en Asesores Expertos). Conduce a la ejecución de la función OnTrade. En MQL4 no hay equivalente de este evento y función.
  • BookEvent sucede cuando cambie la profundidad de mercado (sólo se usa en Asesores Expertos). Conduce a la ejecución de la función OnBookEvent. En MQL4 no hay equivalente de este evento y función, así como profundidad de mercado.
  • ChartEvent sucede cuando el usuario trabaja con tabla: al hacer clic con el ratón y pulsar teclas cuando la ventana de la tabla esté en el foco. También ocurre durante la creación, traslado o supresión de los objetos gráficos, etc. (sólo se usa en Asesores Expertos e Indicadores). Conduce a la ejecución de la función OnChartEvent . No hay equivalente de este evento y función en MQL4.
  • Timer sucede periodicamente cuando el temporizador se activa, si ha sido activado usando la función EventSetTimer. Conduce a la ejecución de la función OnTimer . En MQL4 no hay equivalente de este evento y función, así como temporizador.

Antes de usar las variables, es necesario especificar los tipos de datos de cada uno de ellos. MQL5 es compatible con más tipos de datos que MQL4:

  • bool está destinado a almacenar los valores lógicos (verdadero o falso). Requiere 1 byte de memoria.
  • char está destinado a almacenar valores enteros de -128 a 127. Requiere 1 byte de memoria.
  • uchar está destinado a almacenar valores enteros de 0 a 255. Requiere 1 byte de memoria.
  • short está destinado a almacenar valores enteros de -32 768 a 32 767. Requiere 2 bytes de memoria.
  • ushort está destinado a almacenar valores enteros sin signos de 0 a 65 535. Requiere 2 bytes de memoria.
  • int está destinado a almacenar valores enteros de -2 147 483 648 a 2 147 483 647. Requiere 4 bytes de memoria.
  • uint está destinado a almacenar valores enteros de 0 a 4 294 967 295. Requiere 4 bytes de memoria.
  • long está destinado a almacenar valores enteros de -9 223 372 036 854 775 808 a 9 223 372 036 854 775 807. Requiere 8 bytes de memoria.
  • ulong está destinado a almacenar valores enteros de 0 a 18 446 744 073 709 551 615. Requiere 8 bytes de memoria.
  • float está destinado a almacenar valores de coma flotante. Requiere 4 bytes de memoria.
  • double está destinado a almacenar valores de coma flotante. Normalmente se usa para almacenar datos de precios. Requiere 8 bytes de memoria.
  • datetime está destinado a almacenar valores de hora y fecha, es un número de segundos transcurridos desde 01.01.1970 00:00:00. Requiere 8 bytes de memoria.
  • color está destinado a almacenar la información sobre el color. Contiene las características de tres componentes de color (rojo, verde y azul). Requiere 4 bytes de memoria.
  • enum significa enumeración. Permite especificar un tipo de conjunto determinado limitado de datos. Requiere 4 bytes de memoria.
  • string está destinado a almacenar secuencias de texto. Su representación interna es una estructura de 8 bytes que contiene el tamaño del buffer con secuencia y el puntero a ese buffer.

Es necesario elegir el tipo de datos apropiado para el rendimiento óptimo y el uso racional de memoria. En MQL5 hay un nuevo concepto llamado estructura. La estructura combina los datos relacionados lógicamente.

Sistema de trading

El sistema de trading, que se usa en este artículo como un ejemplo, se basa en la asunción de que las instituciones financieras europeas abren por la mañana y más tarde, los eventos económicos se publican en EE.UU., lo que conduce a la tendencia de EURUSD. El período de la tabla no es importante, pero recomiendo usar las barras de minutos porque todo el día (o parte) es visible de una vez, así que es muy conveniente para la observación.

Ilustración 7. Sistema de trading

A las 7 AM (hora del servidor) las órdenes pendientes de Buy Stop y Sell Stop están situados a distancia de un punto más allá del rango de precios del día corriente. En el caso de las órdenes pendientes de Buy Stop, se toma en cuenta la difusión. Los niveles StopLoss están situados en los lados opuestos al rango. Tras ejecución, la orden StopLoss se mueve a promedio móvil simple, pero sólo si es rentable.

El beneficio de este tipo de trailing en comparación con el clásico Trailing Stop es el siguiente: permite evitar el cierre anticipado de la posición en caso de picos de precios con correcciones. Por otro lado, conduce al cierre de posición cuando la tendencia termina y comienza el movimiento plano. El promedio móvil simple se calcula usando datos de minutos de la tabla y tiene un período de promedio igual a 240.

El nivel de rentabilidad depende de la volatilidad actual del mercado. Para determinar la volatilidad del mercado se usa el indicador Promedio de rango verdadero - ATR (con período igual a 5 se aplica a la tabla diaria). Así pues, muestra el rango de promedio diario de la semana anterior. Para determinar el valor del nivel de Take Profit para la posición larga, añadiremos el valor del indicador ATR al precio mínimo del día actual. Lo mismo para las posiciones cortas: restaremos el valor del indicador ATR del precio máximo del día actual. La orden no se coloca si el valor del precio de la orden supera los niveles de StopLoss y TakeProfit. Después de las 7 PM (hora del servidor) todas las órdenes pendientes se eliminan y no se colocan este día (las posiciones abiertas todavía se arrastran hasta el cierre).


Escribir un indicador

Escribamos un indicador, el cual muestra los niveles de aprovechamiento del sistema comercial descrito anteriormente.

Si el primer símbolo en una línea es "#", esto significa que esta secuencia es una directiva preprocessor. Las directivas se usan para especificar las propiedades adicionales del programa, para declarar constantes, para incluir archivos de cabecera y funciones importadas. Tenga en cuenta que después de las directivas de preprocesador no hay punto y coma (;).

#property copyright   "2010, MetaQuotes Software Corp."
#property link        "http://www.mql5.com"
#property description "Este indicador cácula niveles de TakeProfit"
#property description "utilizando la volatilidad media del mercado. Usa los valores"
#property description "del indicador Average True Range (ATR), calculado"
#property description "sobre datos de precio diario. Los valores del indicador se calculan"
#property description "usando los valores de precio máximos y mínimospor día."
#property version     "1.00"

La información sobre el autor y su página web se pueden especificar en el copyright y en las propiedades de enlace, la propiedad de descripción permite añadir una breve descripción, la propiedad de versión le permite especificar la versión del programa. Cuando el indicador se está ejecutando esta información se muestra de la siguiente manera:

Ilustración 8. Información del indicador.

Si es necesario especificar la posición de los indicadores: en una tabla o en una ventana separada. Se puede hacer especificando una de las propiedades: indicator_chart_window o indicator_separate_window:

#property indicator_chart_window

Además, es necesario especificar el número de buffers de indicadores que se utilizarán y el número de series gráficas. En nuestro caso, hay dos líneas, cada una tiene su propio buffer (un array con los datos que se representarán).

#property indicator_buffers 2
#property indicator_plots   2

Para cada línea del indicador, especifiquemos las siguientes propiedades: tipo (indicator_type property), color (indicator_color property), estilo de dibujo (indicator_style property) y etiqueta de texto (indicator_label property):

#property indicator_type1   DRAW_LINE
#property indicator_color1  C'127,191,127'
#property indicator_style1  STYLE_SOLID
#property indicator_label1  "Buy TP"
#property indicator_type2   DRAW_LINE
#property indicator_color2  C'191,127,127'
#property indicator_style2  STYLE_SOLID
#property indicator_label2  "Sell TP"

Los tipos básicos de línea son: DRAW_LINE - para líneas, DRAW_SECTION - para secciones, DRAW_HISTORAM para los histogramas. Hay otros muchos estilos de dibujo. Se puede definir el color especificando el brillo de sus tres componentes RGB o usando los colores predefinidos, por ejemplo, rojo, verde, azul, blanco, etc. Los estilos de línea son: STYLE_SOLID - línea sólida, STYLE_DASH - línea discontinua, STYLE_DOT - línea de puntos, STYLE_DASHDOT - línea discontinua de puntos, STYLE_DASHDOTDOT - guion-dos puntos.

Ilustración 9. Descripción de las líneas del indicador.

Al usar el modificador de entrada, especifiquemos las variables externas (puede especificar sus valores después de iniciar el indicador), el tipo y los valores por defecto:

entrada int             ATRper       = 5;         //ATR Period
entrada ENUM_TIMEFRAMES ATRtimeframe = PERIOD_D1; //Marco de tiempo del indicador

Se pueden especificar los nombres de los parámetros en los comentarios - serán visibles en lugar de los nombres de las variables:

Ilustración 10. Parámetros de entrada deindicador.

En un nivel global (que es visible a todas las funciones), especificaremos las variables (y sus tipos), que serán utilizadas por diferentes funciones de nuestro indicador.

double bu[],bd[];
int hATR;

Los arrays bu[] y bd[] se usarán para las líneas superiores e inferiores del indicador. Utilizaremos arrays dinámicos (es decir, los arrays sin número especificado de elementos), ya que no sabemos exactamente el número de elementos que se van a utilizar (su tamaño se asignará de forma automática). El código del indicador técnico incorporado se almacenará en la variable hATR. El código del indicador es necesario para usar el indicador.

La función OnInit se activa tras el funcionamiento del indicador (tras adjuntarlo a la tabla).

void OnInit()
  {
   SetIndexBuffer(0,bu,INDICATOR_DATA);
   SetIndexBuffer(1,bd,INDICATOR_DATA);
   hATR=iATR(NULL,ATRtimeframe,ATRper);
  }

La función SetIndexBuffer es necesaria para especificar que los arrays bu[] y bd[] son los buffers del indicador, los cuales se usarán para almacenar los valores de los indicadores. Los valores se representan como líneas del indicador. El primer parámetro define el índice del buffer del indicador, la orden comienza de 0. El segundo parámetro especifica un array, asignad al buufer del indicador. El tercer parámetro especifica el tipo de dato, almacenado en el buffer del indicador: INDICATOR_DATA - datos para representación, INDICATOR_COLOR_INDEX - color de dibujo, INDICATOR_CALCULATIONS - buffers auxiliares para los cálculos intermedios.

el código del indicador, devuelto por la función iATR, se almacena en la variable hATR. El primer parámetro de la función iATR es el símbolo comercial, NULL - es el símbolo de la tabla actual. El segundo parámetro especifica el período de la tabla, que se usa para el cálculo del indicador. El tercer parámetro es el período medio del indicador ATR.

La función OnCalculate se activa al final de la ejecución de la función OnInit y después de la llegada de una nueva cotización para el símbolo actual. Hay dos maneras de activas esta función. Una de ellas, usada por nuestro indicador, es de la siguiente manera:

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])

Después de activar la función OnCalculate, el terminal del cliente pasa los siguientes parámetros:  rates_total - número de barras en la tabla actual, prev_calculated  - número de barras ya calculado por el indicador, time[], open[], high[], low[], close[], tick_volume[], volume[],spread[] - arrays que contienen tiempo, abierto, alto, bajo, cerrado, tick_volume, volumen y valores de difusión para cada barra, respectivamente. Para reducir el tiempo de cálculo, no es necesario volver a calcular los valores de los indicadores que ya han sido calculados y no han cambiado. Después de la activación de la función OnCalculate, vuelve al número de barras que se había calculado previamente.

El código de la función OnCalculate se adjunta entre paréntesis. Empieza con variables locales que se usan en la función (sus tipos y nombres).

  {
   int i,day_n,day_t;
   double atr[],h_day,l_day;

La variable i se usa como contador de ciclos, las variables day_n y day_t se utilizan para almacenar el número de días y para almacenar temporalmente el número de días en el cálculo de los valores de precio máximo y mínimo durante el día. El array atr[] se usa para almacenar los valores del indicador ATR indicator, y las variables h_day y l_day se usan para almacenar los valores de precio máximo y mínimo durante el día.

Primero, tenemos que copiar los valores del indicador ATR en el array atr[] usando la función CopyBuffer. Usaremos el código del indicador ATR como el primer parámetro de esta función. El segundo parámetro es el número de buffers del indicador (la numeración comienza desde 0). El indicador de ATR tiene un único buffer. El tercer parámetro especifica el número del primer elemento con el que empezar, la indexación se realiza del presente al pasado, el elemento cero corresponde a la bara actual (incompleta). El cuarto parámetro especifica el número de elementos que se deben copiar.

Copiemos dos elementos, ya que nos interesa el penúltimo elemento, que corresponde a la última barra (completa). El último parámetro es el array meta para copiar datos.

   CopyBuffer(hATR,0,0,2,atr);

La dirección de la indexación del array depende de la etiqueta AS_SERIES Si se ha establecido (es decir, igual a verdadero), el array se considera como serie de tiempo, la indexación de los elementos se realiza a partir de los datos más recientes a más antiguos. Si no se ha establecido (es decir, igual a falso), los elementos más antiguos tienen un índice más bajo y los más nuevos más alto.

   ArraySetAsSeries(atr,true);

Para el array atr[] establecemos la etiqueta AS_SERIES para usar verdaderamente la función ArraySetAsSeries (el primer parámetro de esta función es el array, para el que se debe cambiar la etiqueta, el segundo parámetro es el valor de nueva etiqueta). Ahora, el índice de la barra de corriente (incompleta) es igual a 0, el índice de la penúltima barra (completa) es igual a 1.

El operador for permite crear un bucle.

   for(i=prev_calculated;i<rates_total;i++)
     {
      day_t=time[i]/PeriodSeconds(ATRtimeframe);
      if(day_n<day_t)
        {
         day_n=day_t;
         h_day=high[i];
         l_day=low[i];
        }
        else
        {
         if(high[i]>h_day) h_day=high[i];
         if(low[i]<l_day) l_day=low[i];
        }
      bu[i]=l_day+atr[1];
      bd[i]=h_day-atr[1];
     }

Después del operador for, el primer operador entre paréntesis es un enunciado: i=prev_calculated. Lo siguiente es una expresión, en nuestro caso es: i<rates_total. Es una condición de bucle - el bucle se ejecuta mientras sea verdadero. El tercero es un enunciado que se ejecuta después de cada ejecución del bucle. En nuestro caso, es i++ (es igual a i=i+1 y significa que se incrementa la variable i mediante 1)

En nuestro bucle, la variable i varía del valor prev_calculated al valor, igual a rates_total-1 con paso 1. Los arrays de datos históricos (time[], high[] and low[]) no son series de tiempo por defecto, el índice cero corresponde a la barra más antigua del historial, el último corresponde a la barra incompleta actual. En bucle, todas las barras desde la primera no calculada (prev_calculated) hasta la última barra (rates_total-1) se procesan. Para cada una de estas barras se calculan los valores de nuestro indicador.

Los valores de tiempo en el array time[] se almacenan como número de segundos, desde 01.01.1970 00:00:00. Si lo dividimos por el número de segundos en un día (o en algún otro período), la parte entera del resultado será el número del día empezando por 01.01.1970 (o algún otro período de tiempo). La función PeriodSeconds devuelve el número de segundos en un período de tiempo, el cual es definido como un parámetro. La variable day_t es el número de día, correspondiente a la barra con el índice i. La variable day_t es el número de día, para el que se calculan los valores de precio más altos y más bajos.

Consideremos el operador si. Si la expresión entre paréntesis de este operador es verdadera, se ejecuta el operador siguiendo la palabra clave si. Si es falsa, se ejecuta el operador siguiendo la palabra clave otro. Cada operador puede ser compuesto, es decir, puede contener varios operadores. En nuestro caso, se adjuntan entre paréntesis.

Se almacenan los valores de los precios más altos y más bajos del día procesado en las variables h_day y l_day respectivamente. En nuestro caso, revisaremos la siguiente condición: si la barra analizada corresponde al nuevo día, empezamos a calcular los valores de los precios máximos y mínimos de nuevo, si no, continuamos. Calcularemos los valores para cada línea del indicador: para la línea superior (usamos el precio mínimo del día para la línea inferior), usamos los máximos valores de precio.

Al final de la función OnCalculate, el operador return devuelve el número de barras calculadas.

   return(rates_total);
  }

Dentro de la función DeInit (opera cuando se elimina el indicador de la tabla o cuando se cierra el terminal del cliente) se libera la memoria, reservada por el indicador ATR, usando la función IndicatorRelease. Esta función sólo tiene un parámetro (el código del indicador).

void OnDeinit(const int reason)
  {
   IndicatorRelease(hATR);
  }

Ya está completo nuestro indicador. Para compilarlo, en MetaEditor seleccione Archivo > Compilar o pulse la tecla F7. Si no hay errores en el código, la compilación se realizará con éxito. Los resultados de la compilación se mostrarán en la pestaña Errores o en la ventana de Herramientas. En su caso, el compilador podría mostrar la advertencia "Posible pérdida de datos de conversión" para la siguiente secuencia:

      day_t=time[i]/PeriodSeconds(ATRtimeframe);

En esta línea estamos intencionalmente descartando la parte fraccional, por lo que la pérdida de datos no es un error.

Cuando un indicador se ha completado y compilado, se puede adjuntar a las tablas en el terminal del cliente de MetaTrader 5 o se puede usar en otros Indicadores, Expert Advisors o Scripts. El código fuente de este indicador está disponible como adjunto en este artículo.


Escribir un Expert Advisor

Es hora de escribir un Expert Advisor, el cual implementa el sistema comercial descrito anteriormente. Daremos por supuesto que va a comerciar sólo un instrumento financiero. Para varios Asesores Expertos para comerciar en un solo instrumento, es necesario analizar cuidadosamente la contribución de cada uno de ellos en la posición global, y eso queda fuera del alcance de este artículo.

#property copyright   "2010, MetaQuotes Software Corp."
#property version     "1.00"
#property description "Este Expert Advisor coloca las órdenes pendientes durante el"
#property description "tiempo de StartHour hasta EndHour en los niveles de precios que"
#property description "están 1 punto por debajo del rango de comercio actual."
#property description "Los niveles de StopLoss se colocan en el lugar opuesto"
#property description "del rango de precio. Después de ejecución de orden, el valor TakeProfit"
#property description "se coloca en el nivel  'indicator_TP' . El nivel StopLoss se mueve"
#property description "a los valoresSMA sólo para ódenes rentables."

El propósito de estas directivas de preprocessor ya se han considerado al escribir una sección de Indicador. Funcionan igual que en Expert Advisor.

Especifiquemos los parámetros de los parámetros de entrada (que pueden ser definidos por el usuario tras iniciar un Expert Advisor), sus tipos y los valores por defecto.

input int    StartHour = 7;
input int    EndHour   = 19;
input int    MAper     = 240;
input double Lots      = 0.1;

Los parámetros StartHour y EndHour definen el período de tiempo (horas de inicio y fin) para las órdenes pendientes. El parámetro MAper define el período de media de la media de movimiento simple, el cual se usa para el nivel StopLoss de posición abierta durante su arrastre. El parámetro Lots define el volumen de instrumento financiero usado en el comercio.

Especifiquemos las variables globales que se usaran en las diferentes funciones comerciales:

int hMA,hCI;

La variable hMA se usará para almacenar el código del indicador MA y la variable hCI se usará para almacenar el código del indicador del cliente (es un indicador que se ha escrito anteriormente).

La función OnInit se ejecuta cuando se inicia Expert Advisor

void OnInit()
  {
   hMA=iMA(NULL,0,MAper,0,MODE_SMA,PRICE_CLOSE);
   hCI=iCustom(NULL,0,"indicator_TP");
  }

En esta función obtenemos los códigos del indicador MA y el indicador de nuestro cliente. La función iMA y sus parámetros se usan en la misma función iATR descrita anteriormente.

El primer parámetro de la función iCustom es el nombre simbólico del instrumento, NULL - significa instrumento para la tabla actual. El segundo parámetro - es el marco de tiempo de la tabla, cuyos datos se usan para calcular el indicador, 0 - significa período de tiempo de la tabla actual. El tercer parámetro es el nombre del archivo del indicador (sin extensión). La ruta del archivo es relativa a la carpeta MQL5\Indicators\.

Creemos la función OnTick, la cual se ejecuta tras la llegada de una nueva cotización:

void OnTick()
  {

El código de la función se adjunta entre paréntesis.

Especifiquemos las estructuras de datos predeterminadas que se usarán en Expert Advisor:

MqlTradeRequest request;
MqlTradeResult result;
MqlDateTime dt;
La estructura predefinida MqlTradeRequest tiene parámetros de órdenes y posiciones que se pasan a la función OrderSend en operaciones comerciales. El propósito de la estructura MqlTradeResult es almacenar la información sobre los resultados de la operación comercial devueltos por la función OrderSend. El propósito de la estructura predefinida MqlDateTime es almacenar la información de fecha y hora.

Especifiquemos las variables locales (y sus tipos) que se usarán en la función OnTick :

   bool bord=false, sord=false;
   int i;
   ulong ticket;
   datetime t[];
   double h[], l[], ma[], atr_h[], atr_l[],
          lev_h, lev_l, StopLoss,
          StopLevel=_Point*SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL),
          Spread   =NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK) - SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits);
Las variables booleanas bord y sord se usan como etiquetas que muestran la presencia de órdenes pendientes Buy Stop y Sell Stop. Si hay, la variable correspondiente tiene valor igual a verdadero, si no, es falso. La variable i se usa como contador en operadores de bucle y para almacenar datos intermedios. El ticket para orden pendiente se almacena en la variable ticket.

Los arrays t[], h[] y l[] se usan para almacenar el tiempo, los valores de precio máximo y mínimo para cada barra en los datos del historial. El array ma[] se usa para almacenar valores del indicador MA, los arrays atr_h[] y atr_l[] se usan para almacenar los valor para las líneas superior e inferior del indicador de cliente indicator_TP, el cual hemos creado.

Las variables lev_h y lev_l se usan para almacenar los valores de precio máximo y mínimo del día actual y los precios de apertura para las órdenes pendientes. La variable StopLoss se usa para almacenar temporalmente el precio Stop Loss de la posición abierta.

La variable StopLevel se usa para almacenar el valor de STOP_LEVEL - la distancia mínima entre el precio actual y el precio de la orden pendiente (en unidades de precio). Calculamos este valor como un producto del precio de punto (el valor predefinido _Point) por el valor de la variable STOP_LEVEL, definido en puntos. El valor de STOP_LEVEL es devuelto por la función SymbolInfoInterger. El primer parámetro de esta función es el nombre del símbolo, el segundo es el identificador de la propiedad solicitada. El símbolo (nombre del instrumento financiero) se puede obtener usando la función Símbolo (no tiene parámetros).

El valor Spread se usa para almacenar el valor de margen (en unidades de precio). Su valor se calcula como diferencia entre los valores actuales de Ask y Bid, normalizados usando la función NormalizeDouble. El primer parámetro de esta función es el valor de tipo doble para normalizar, el segundo es el número de dígitos después de punto, el cual hemos obtenido de la variable predefinida _Digits. Los valores actuales de Ask y Bid se pueden obtener usando la función SymbolInfoDouble. El primer parámetro de esta función es el nombre del símbolo, el segundo es el identificador de propiedad.

Rellenemos la solicitud con valores que serán comunes para la mayoría de los códigos de la función OrderSend:

   request.symbol      =Symbol();
   request.volume      =Lots;
   request.tp          =0;
   request.deviation   =0;
   request.type_filling=ORDER_FILLING_FOK;
El elemento request.symbol contiene el nombre simbólico del instrumento que comercia, el elemento request.volume el volumen (tamaño del contrato) del instrumento financiero, request.tp - el valor numérico de TakeProfit (en algunos casos no se usará y se pondrá 0), el request.deviation - desviación permitida del precio durante la ejecución de la operación comercial, elrequest.type_filling - el tipo de orden puede ser uno de los siguientes:
  • ORDER_FILLING_FOK - El acuerdo sólo se puede ejecutar si el volumen es igual o mejor que el especificado en la orden. Si no hay suficiente volumen la orden no se ejecutará.
  • ORDER_FILLING_IOC - no hay suficiente volumen, la orden se ejecutará al máximo valor de mercadodisponible. 
  • ORDER_FILLING_RETURN - lo mismo que ORDER_FILING_IOC, pero en este caso se colocará una orden adicional paravolumen perdido.

Obtengamos la hora actual del servidor (hora de la última cotización) usando la función TimeCurrent. El único parámetro de esta función es el puntero a la estructura con resultado.

   TimeCurrent(dt);

Para todos nuestros cálculos necesitaremos los datos de precios del historial para sólo el día actual. El número de barras que es necesario (y con alguna reserva) se puede calcular usando esta fórmula: i = (dt.hour + 1)*60, donde dt.hour - es el elemento estructural que contiene la hora actual. Los valores de tiempo, precio máximo y mínimo se copian en los arrays t[], h[] y l[] usando las funciones CopyTime, CopyHigh, CopyLow respectivamente:

   i=(dt.hour+1)*60;
   if(CopyTime(Symbol(),0,0,i,t)<i || CopyHigh(Symbol(),0,0,i,h)<i || CopyLow(Symbol(),0,0,i,l)<i)
     {
      Print("Can't copy timeseries!");
      return;
     }

El primer parámetro en las funciones CopyTime, CopyHigh y CopyLow es el nombre del símbolo, el segundo es el marco de tiempo de la tabla, el tercero es el elemento inicial a copiar, el cuarto es el número de elementos a copiar, el quinto es el array meta para los datos. Cada una de las funciones devuelve el número de elementos copiados o el valor negativo igual a -1 en caso de error.

El operador si se usa para comprobar el número de elementos copiados para los tres arrays. Si el número de elementos copiados es más pequeño que lo que se necesita para el cálculo (incluso para uno de los arrays), o para el caso de error, muestra el mensaje "No puede copiar series de tiempo" en el registro Expertos y termina la ejecución de la función OnTick usando el operador return. El mensaje es mostrado por la función Print. Puede imprimir cualquier tipo de datos, separados por comas.

Cuando los datos de precios se hayan copiado a los arrays t[], h[] y l[], establecemos la etiqueta AS_SERIES para usar verdaderamente la función ArraySetAsSeries que se ha considerado anteriormente. Es necesario establecer la indexación de array como serie de tiempo (de los precios actuales a los precios más antiguos):

   ArraySetAsSeries(t,true);
   ArraySetAsSeries(h,true);
   ArraySetAsSeries(l,true);

Los valores de precio máximos y mínimos del día actual se colocan en las variables lev_h y lev_l:

   lev_h=h[0];
   lev_l=l[0];
   for(i=1;i<ArraySize(t) && MathFloor(t[i]/86400)==MathFloor(t[0]/86400);i++)
     {
      if(h[i]>lev_h) lev_h=h[i];
      if(l[i]<lev_l) lev_l=l[i];
     }

El bucle se ejecuta sólo si la condición MathFloor(t[i]/86400) == MathFloor(t[0]/86400) es verdadera para restringir la búsqueda con las barras que pertenecen al día actual. En la parte izquierda de esta expresión, hay un número de barra del día actual, en la derecha el número del día actual (86400 es el número de segundos en un día). La función MathFloor redondea el valor numérico, es decir, usa sólo una parte entera para los valores positivos. El único parámetro de esta función es la expresión a redondear. En MQL5, al igual que en MQL4, la igualdad se define con "==" símbolos (consulte Operaciones con relaciones).

Los precios de la orden se calculan de esta manera: para la orden pendiente del tipo Buy Stop añadiremos un punto (la variable predefinida _Punto es igual al tamaño del punto en unidades de precio) y Spread a la variable lev_h (lev_h+=Spread+_Point o lev_h=lev_h+Spread+_Point). Para las órdenes pendientes de tipo Sell Stop sustraeremos un punto del valor de variable lev_l (lev_l-=_Point o lev_l=lev_l-_Point).

   lev_h+=Spread+_Point;
   lev_l-=_Point;

A continuación, copiaremos los valores de los buffers de los indicadores a los arrays usando la función CopyBuffer. Los valores MA se copian al array ma[], los valores de la línea superior del indicador de nuestro cliente se copian al array atr_h[], los valores de la línea inferior del indicador se copian al array atr_l[]. La función CopyBuffer se ha descrito anteriormente cuando hemos considerado los detalles del indicador.

   if(CopyBuffer(hMA,0,0,2,ma)<2 || CopyBuffer(hCI,0,0,1,atr_h)<1 || CopyBuffer(hCI,1,0,1,atr_l)<1)
     {
      Print("Can't copy indicator buffer!");
      return;
     }

Necesitamos el valor del indicador MA que corresponde a la penúltima barra (última completa) y el valor de nuestro indicador que corresponde a la última barra. Por lo tanto, copiaremos estos dos elementos al array ma[] y un elemento a los arrays atr_h[] y atr_l[]. En caso de error al copiar o si el número de valores copiados en menor que el necesitado (para cualquiera de esos arrays), el mensaje se muestra en el registro Expertos y la función OnTick se termina usando el operador return.

Para el array ma[], estableceremos la etiqueta AS_SERIES que indica la indexación de series de tiempo del array.

   ArraySetAsSeries(ma,true);

Los arrays atr_[] y atr_l[] tienen sólo un elemento, por lo que la indexación de series de tiempo no es importante. Dado que el valor atr_l[0] se utilizará además para determinar el nivel de TakeProfit, las posiciones cortas están cerradas en el precio Ask, pero añadiremos la difusión al valor de atr_l[0], puesto que los precios Bid se usan en las tablas de precios.

   atr_l[0]+=Spread;

La función PositionsTotal devuelve el número de posiciones abiertas (no tiene parámetros). Los índices de las posiciones comienzan desde 0. Creemos un bucle que busque todas las posiciones abiertas:

// en este bucle buscaremos todas las posiciones abiertas
   for(i=0;i<PositionsTotal();i++)
     {
      // processing orders with "our" symbols only
      if(Symbol()==PositionGetSymbol(i))
        {
         // cambiaremos las valores de StopLoss y TakeProfit
         request.action=TRADE_ACTION_SLTP;
         // long positions processing
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
           {
            // let's determine StopLoss
            if(ma[1]>PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]; else StopLoss=lev_l;
            // if StopLoss is not defined or lower than needed            
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(StopLoss-PositionGetDouble(POSITION_SL),_Digits)>0
               // si TakeProfit no está definido o es mayor de lo necesario
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(PositionGetDouble(POSITION_TP)-atr_h[0],_Digits)>0)
               // ¿está el nuevo StopLoss cerca del precio actual?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLoss-StopLevel,_Digits)>0
               // ¿está el nuevo TakeProfit cerca del precio actual?
               && NormalizeDouble(atr_h[0]-SymbolInfoDouble(Symbol(),SYMBOL_BID)-StopLevel,_Digits)>0)
              {
               // poniendo nuevo valor de StopLoss a la estructura
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // poniendo nuevo valor de TakeProfit a la estructura
               request.tp=NormalizeDouble(atr_h[0],_Digits);
               // enviando solicitud al servidor comercial
               OrderSend(request,result);
              }
           }
         // posiciones cortas procesando
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            // determinemos el valor de StopLoss
            if(ma[1]+Spread<PositionGetDouble(POSITION_PRICE_OPEN)) StopLoss=ma[1]+Spread; else StopLoss=lev_h;
            // si StopLoss no está definido o es mayor de lo necesario
            if((PositionGetDouble(POSITION_SL)==0 || NormalizeDouble(PositionGetDouble(POSITION_SL)-StopLoss,_Digits)>0
               // si TakeProfit no está definido o es mayor de lo necesario
               || PositionGetDouble(POSITION_TP)==0 || NormalizeDouble(atr_l[0]-PositionGetDouble(POSITION_TP),_Digits)>0)
               // ¿está el nuevo StopLoss cerca del precio actual?
               && NormalizeDouble(StopLoss-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0
               // ¿está el nuevo TakeProfit cerca del precio actual?
               && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_ASK)-atr_l[0]-StopLevel,_Digits)>0)
              {
               // poniendo el nuevo valor de StopLoss a la estructura
               request.sl=NormalizeDouble(StopLoss,_Digits);
               // poniendo el nuevo valro de TakeProfit a la estructura
               request.tp=NormalizeDouble(atr_l[0],_Digits);
               // enviando solicitud al servidor comercial
               OrderSend(request,result);
              }
           }
         // si hay una posición abierta, devolver desde aquí...
         return;
        }
     }
Usando el operador si en el bucle, seleccionamos las posiciones que se han abierto para el símbolo de la tabla actual. La función PositionGetSymbol devuelve el símbolo del instrumento, además, selecciona automáticamente la posición con la que trabajar. La función sólo tiene un parámetro - la posición de índice en la lista de posiciones abiertas. Es bastante posible que se tenga que cambiar los valores StopLoss y TakeProfit de las posiciones abiertas, así que pongamos el valor TRADE_ACTION_SLTP al elemento request.action. A continuación, dependiendo de su dirección, las posiciones se dividen entre largas y cortas.

Para las posiciones largas el nivel StopLoss se determina así; Si el indicador MA es mayor que el precio de apertura de la posición, el valor StopLoss se supone que es igual que el valor del indicador MA, si no, el valor StopLoss se supone que es igual que el valor de la variable lev_l. El valor actual StopLoss para la posición abierta se determina usando la función PositionGetDouble, que tiene sólo un parámetro - el identificador de propiedad de la posición. Si el valor StopLoss no se define para la posición abierta (igual a 0) o es mayor de lo que debiera, modificaremos los valores de StopLoss y TakeProfit para esta posición. Si el valor TakeProfit no está definido (igual a 0) o es mayor de lo que debiera (mayor que la línea superior de nuestro indicador), modificaremos los valores de StopLoss y TakeProfit para esta posición.

Tenemos que comprobar la posibilidad de cambiar los valores de StopLoss y TakeProfit. El nuevo valor de StopLoss nunca debe ser inferior al precio actual Bid (al menos por el valor STOP_LEVEL), el nuevo valor de TakeProfit nunca debe ser superior al precio actual Bid al menos por el valor STOP_LEVEL. Hemos utilizado la diferencia normalizada para comparar, debido a que los valores comparados pueden diferir en últimos dígitos debido a la inexactitud causada por la conversión de números binarios de coma flotante de doble tipo en números decimales de coma flotante.

Si es necesario cambiar los niveles StopLoss y TakeProfit para posiciones abiertas y los nuevos valores son válidos según las normas de trading, ponemos los nuevos valores de StopLoss y TakeProfit en los elemenos correspondientes de la estructura y activamos la función OrderSend para enviar los datos al servidor comercial.

El cambio de valores de StopLoss y TakeProfit para las posiciones cortas es el mismo. Las posiciones cortas, en comparación con las largas, están cerradas por precios Ask, por lo que los valores Ask se usarán para comparación. Si hay una posición abierta para la tabla actual, terminamos la ejecución de la función OnTick usando el operador return.

La función OrdersTotal (no tiene parámetros) devuelve el número de órdenes pendientes. El índice empieza de 0. Creemos un bucle que se usará para procesar todas las órdenes pendientes:

// en este bucle comprobaremos todas las órdenes pendientes
   for(i=0;i<OrdersTotal();i++)
     {
      // seleccionando cada orden y obteniendo su ticket
      ticket=OrderGetTicket(i);
      // procesando órdenes sólo con "nuestros" símbolos
      if(OrderGetString(ORDER_SYMBOL)==Symbol())
        {
         // procesando órdenes Buy Stop
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_BUY_STOP)
           {
            // compruebe si hay tiempo de trading y si es posible el movimiento de precio
            si(dt.hour>=StartHour && dt.hour<EndHour && lev_h<atr_h[0])
              {
               // si el precio de apertura es menor de lo necesario
               if((NormalizeDouble(lev_h-OrderGetDouble(ORDER_PRICE_OPEN),_Dígitos)>0
                  // si StopLoss no está definido o es mayor de lo necesario
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(OrderGetDouble(ORDER_SL)-lev_l,_Digits)!=0)
                  // ¿está el precio de apertura cerca del precio actual?
                  && NormalizeDouble(lev_h-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0)
                 {
                  // se cambiarán los parámetros de las órdenes pendientes
                  request.action=TRADE_ACTION_MODIFY;
                  // poniendo el número de ticket a la estructura
                  request.order=ticket;
                  // poniendo el nuevo valor del precio a la estructura
                  request.price=NormalizeDouble(lev_h,_Digits);
                  // poniendo nuevo valor de StopLoss a la estructura
                  request.sl=NormalizeDouble(lev_l,_Digits);
                  // enviando solicitud al servidor comercial
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // si no hay tiempo de trading o el comercio medio ha combiado
            else
              {
               // eliminaremos esta orden pendiente
               request.action=TRADE_ACTION_REMOVE;
               // poniendo el número de ticket a la estructura
               request.order=ticket;
               // enviando solicitud al servidor comercial
               OrderSend(request,result);
               // exiting from the OnTick() function
               return;
              }
            // estableciendo la etiqueta que indica la presencia de la orden Buy Stop
            bord=true;
           }
         // procesando órdenes Sell Stop
         if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
           {
            // compruebe si hay tiempo de trading y movimiento de precio si es posible
            if(dt.hour>=StartHour && dt.hour<EndHour && lev_l>atr_l[0])
              {
               // si el precio de apertura es mayor de lo necesario
               if((NormalizeDouble(OrderGetDouble(ORDER_PRICE_OPEN)-lev_l,_Digits)>0
                  // si StopLoss no está definido o es inferior de lo necesario
                  || OrderGetDouble(ORDER_SL)==0 || NormalizeDouble(lev_h-OrderGetDouble(ORDER_SL),_Digits)>0)
                  // ¿está el precio de apertura cerca del precio actual?
                  && NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-lev_l-StopLevel,_Digits)>0)
                 {
                  // los parámetros de la orden pendiente se cambiarán
                  request.action=TRADE_ACTION_MODIFY;
                  // poniendo ticket de la orden modificada a la estructura
                  request.order=ticket;
                  // poniendo nuevo valor del precio de apertura a la estructura
                  request.price=NormalizeDouble(lev_l,_Digits);
                  // poniendo nuevo valor de StopLoss a la estructura
                  request.sl=NormalizeDouble(lev_h,_Digits);
                  // sending request to trade server
                  OrderSend(request,result);
                  // exiting from the OnTick() function
                  return;
                 }
              }
            // si no hay tiempo de trading o el comercio medio se ha cambiado
            else
              {
               // eliminaremos esta orden pendiente
               request.action=TRADE_ACTION_REMOVE;
               // poniendo el número de ticket a la estructura
               request.order=ticket;
               // enviando solicitud al servidor comercial
               OrderSend(request,result);
               // saliendo de la función OnTick() 
               return;
              }
            // estableciendo la etiqueta que indica la presencia de la orden Sell Stop
            sord=true;
           }
        }
     }
Al usar la función OrderGetTicket saleccionamos la orden con la que seguir trabajando y guardaremos el ticket de la orden en la variable ticket. La función sólo tiene un parámetro (índice de la orden en la lista de órdenes abiertas). La función OrderGetString se usa para obtener el nombre del símbolo. Tiene sólo un parámetro (el identificador de propiedad de la orden). Compararemos el nombre del símbolo con el nombre de la tabla actual, esto permite seleccionar órdenes por instrumento en el que Expert Advisor está trabajando. El tipo de orden es determinado por la función OrderGetInteger con el identificador de tipo de orden correspondiente. Procesaremos por separado las órdenes Buy Stop y Sell Stop.

Si la hora actual está en el rango desde StartHour hasta EndHour y el precio de apertura de la orden Buy Stop no excede la línea superior del indicador, modificaremos el precio de apertura y el valor de nivel StopLoss (si es necesario), si no, eliminaremos la orden.

A continuación determinaremos si es necesario modificar el precio de apertura o en nivel StopLoss para la orden pendiente. Si el precio de apertura de Buy Stop es inferior de lo necesario o si StopLoss no está definido o es superior, pondremos el valor TRADE_ACTION_MODIFY en el elemento request.action (significa que se deben cambiar los parámetros de orden pendiente). También pondremos el ticket de la orden en el elemento request.ticket y enviamos la solicitud comercial al servidor comercial usando la función OrderSend. En comparación, determinamos el caso en que el precio de apertura es menor de lo que debería ser, ya que el valor de difusión puede variar, pero no modificamos la orden después de cada cambio de difusión, se establece en el nivel más alto, que corresponde a el valor de difusión máximo.

También en comparación determinaremos solo la casilla cuando el valor de StopLoss es superior de lo que debería ser puesto que el rango de precio se podría expandir durante el día y es necesario bajar el valor de la orden StopLoss tras las nuevas bajadas del precio. Tras enviar la solicitud al servidor comercial, la ejecución de la función OnTick se termina usando el operador return. Si las órdenes Buy Stop están presentes, la variable bord se establece en verdadero.

Las órdenes Sell Stop se procesan igual que las órdenes Buy Stop.

Ahora coloquemos las órdenes pendientes Buy Stop y Sell Stop en la casilla de su ausencia. Pondremos el valor TRADE_ACTION_PENDING en el elemento request.action (significa que la orden pendiente se ha colocado).

   request.action=TRADE_ACTION_PENDING;

Si el valor de la hora actual está dentro del tiempo de colocación, colocaremos las órdenes:

   si(dt.hour>=StartHour && dt.hour<EndHour)
     {
      si(bord==false && lev_h<atr_h[0])
        {
         request.price=NormalizeDouble(lev_h,_Digits);
         request.sl=NormalizeDouble(lev_l,_Digits);
         request.type=ORDER_TYPE_BUY_STOP;
         OrderSend(request,result);
        }
      if(sord==false && lev_l>atr_l[0])
        {
         request.price=NormalizeDouble(lev_l,_Digits);
         request.sl=NormalizeDouble(lev_h,_Digits);
         request.type=ORDER_TYPE_SELL_STOP;
         OrderSend(request,result);
        }
     }
  }
Mientras que colocamos las órdenes Buy Stop y Sell Stop en las que estamos comprobando la presencia del mismo orden mediante el análisis de los valores. También comprobaremos la siguiente condición: el precio de la orden debe estar dentro de los valores del indicador. El precio normalizado de orden se coloca en el elemento request.price, el valor normalizado de StopLoss en la variable request.sl, el tipo de orden (ORDER_BUY_STOP or ORDER_SELL_STOP) se coloca en la variable request.type. Después de esto, enviaremos la solicitud al servidor comercial. El código de la función OnTick termina con un punto y coma.

Los recursos asignados por los indicadores, se liberan dentro de la función OnDeinit usando la función IndicatorRelease que se ha considerado anteriormente.

void OnDeinit(const int reason)
  {
   IndicatorRelease(hCI);
   IndicatorRelease(hMA);
  }

Se ha completado el Expert Advisor, la compilación debe ser satisfactoria si no hay ningún error. Ahora podemos ejecutarlo adjuntándolo a la tabla. El código fuente puede descargarse en los archivos adjuntos de este artículo.


Inicio y depuración

Cuando el Expert Advisor y el Indicador estén preparados, consideremos cómo iniciarlos y cómo depurarlos usando el depurador MetaEditor.

Para iniciar el Expert Advisor, es necesario encontarlo en el grupo Expert Advisors de la ventana Navegador. Después de eso, seleccione Adjuntar a la tabla desde el menú de contexto que aparecerá al hacer clic con el botón derecho:

Ilustración 11. Iniciar Expert Advisor.

Aparecerá la ventana con parámetros de entrada de Expert Advisor. Puede cambiar estos parámetros si es necesario. Tras pulsar OK, aparecerá el icono en el parte superior derecha de la tabla. Este icono indica que el Expert Advisor está funcionando. Para mostrar o cerrar la ventana Navegador, puede seleccionar Navegador desde el menú Visualizar o pulsar Ctrl+N. La segunda manera de iniciar el Expert Advisor es seleccionarlo en el sub-menú Experts del menú Insertar. 

Para que se pueda comerciar con el Expert Advisor, se debe habiltar AutoTrading en las opciones del terminal del cliente: Menú Herramientas -> ventana Opciones -> pestaña Expert Advisors -> Habilitar AutoTrading. Para que el Expert Advisor puede activar las funciones desde DLL, la opción Permitir importaciones de DLL se debe habilitar.

Ilustración 12. Opciones del terminal - habilitar AutoTrading

Además, puede establecer autorización o prohibición de trading e importaciñon de las bibliotecas externas DDL para cada Expert Advisor individualmente comprobando las opciones correspondientes.

A pesar del hecho de que nuestro Expert Advisor usa indicadores, las líneas de los indicadores no se representan en la tabla. Si es necesario, puede adjuntar los indicadores manualmente.

Iniciar indicadores es igual para Expert Advisors: si desea iniciar indicadores integrados, expanda el árbol de Indicadores en la ventana Navegador (para los indicadores del cliente es necesario expandir el árbol Indicadores de Cliente), haga clic con el botón derecho para que aparezca el menú emergente y seleccione Adjuntar a la tabla desde el menú de contexto. La segunda manera es seleccionar Indicadores en el menú Insertar, seleccione el grupo (o seleccione Cliente para indicadores de cliente) y el indicador.

Los Scripts se inician de la misma manera que los Experts Advisors e Indicadores.

La información sobre los eventos del terminal del cliente (conexión/desconexión a/desde el servidor comercial, actualización automática, cambios de posiciones y órdenes, funcionamiento de Expert Advisors y Scripts, mensajes de error) se pueden encontrar en la pestaña Diario de la ventana Herramientas. Los mensajes mostrados por los Expert Advisors, los Indicador y los Scripts se encuentran en la pestaña Experts.

El MetaEditor tiene un depurador incorporado. Le permite depurar programas - ejecución paso por paso de Expert Advisors, Indicadores y Scripts. La depuración ayuda a encontrar errores en el código del programa y a observar el proceso durante la ejecución de Expert Advisor, Indicador o Script. Para ejecutar un programa en modo depuración, es necesario seleccionar Inicio en el menú Depuración o pulsar la tecla F5. El programa se compilará y se ejecutará en modo depuración en la tabla separada, su período y símbolo se pueden especificar en la pestaña Depuración de la ventana Opciones de MetaEditor.

Ilustración 13. Opciones del editor - Depuración.

Puede establecer puntos de ruptura pulsando la tecla F9 o haciendo doble clic en la parte izquierda de la línea o seleccionando Depuración > Alternar punto de ruptura. En modo depuración, la ejecución del programa se detendrá antes del operador con punto de ruptura. Tras la parada, aparecerá la pestaña Depuración en la ventana Herramientas (Ilustración 14). En la parte izquierda hay un panel de pila de activaciones - archivo, función y número se muestran ahí. En la parte derecha hay un panel de observación - los valores de las variables observadas se muestran aquí. Para añadir la variable a la lista de observación, haga clic con el botón derecho en el panel y seleccione Añadir o pulse la tecla Insertar.

Ilustración 14. Depuración del programa.

La ejecución del programa paso por paso se puede realizar pulsando F11, F10, o las teclas Shift+F11. Tras pulsar la tecla F11 o seleccionar Step Into en el menú Depuración, pasará un paso de la ejecución del programa introduciendo todas las funciones activadas. Tras pulsar la tecla F11 o seleccionar Step Over en el menú Depuración, pasará un paso de la ejecución del programa sin introducir funciones activadas. Tras pulsar las teclas Shift+F11 o seleccionar Step Over en el menú Depuración, se ejecutará un paso del programa un nivel más alto. La flecha verde en el lado izquierdo del código marca la línea del código que se ejecutará.


Conclusión

En el artículo se presenta un ejemplo sobre cómo escribir un Expert Advisor y un Indicador y se describen las bases del lenguaje de programación MQL5. El sistema de trading, provisto aquí se selecciona como ejemplo, por lo que el autor no es responsable de su utilización en el comercio real.


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

Archivos adjuntos |
expert.mq5 (23.48 KB)
indicator_tp.mq5 (4.45 KB)
Usando los punteros de objeto en MQL5 Usando los punteros de objeto en MQL5
Por defecto, todos los objetos en MQL5 se pasan por referencia, pero hay una posibilidad de usar los punteros de objeto. Sin embargo, es necesario realizar una comprobación del puntero ya que el objeto puede no ser inicializado. En este caso, el programa MQL5 terminará con un error crítico y descargado. Los objetos, creados automáticamente, no causan este error, por lo que, en esencia, son muy seguros. En este artículo intentaremos comprender la diferencia entre la referencia del objeto y el puntero del mismo y veremos cómo escribir código seguro con el uso de punteros.
MQL5 para Principiantes: Guía para el Uso de Indicadores Técnicos en Asesores Expertos MQL5 para Principiantes: Guía para el Uso de Indicadores Técnicos en Asesores Expertos
Para obtener valores en un indicador incorporado o personalizado en un Asesor Experto, en primer lugar, su identificador se debe crear usando la función correspondiente. Los ejemplos de este artículo muestran cómo usar un indicador técnico mientras crea sus propios programas. El artículo describe indicadores creados con el lenguaje MQL5. Está pensado para aquellos que no tienen mucha experiencia en el desarrollo de estrategias de trading, y ofrece formas sencillas y claras de trabajar con indicadores usando la biblioteca de funciones facilitada.
Indicadores personalizados para principiantes en MQL5 Indicadores personalizados para principiantes en MQL5
Cualquier materia parece complicada y difícil de aprender para un principiante. Materias que ahora nos parecen muy simples y claras. Pero no olvidemos que todos tenemos que aprender desde cero, incluso nuestro propio idioma. Lo mismo ocurre con el lenguaje de programación MQL5 que ofrece grandes posibilidades para desarrollar nuestras propias estrategias de trading. Podemos empezar a aprenderlo comenzando con nociones más básicas y los ejemplos más sencillos. En este artículo vamos a considerar la interacción de un indicador técnico con el terminal de cliente con un ejemplo de indicador personalizado SMA.
Cómo realizar un robot de trading en poco tiempo Cómo realizar un robot de trading en poco tiempo
Operar en los mercados financieros conlleva muchos riesgos, incluido el más importante: el riesgo de tomar una decisión equivocada. El sueño de todo operador es conseguir un robot de trading que esté siempre en buena forma y evite los errores humanos como el miedo, la avaricia y la impaciencia.