English Русский 中文 Deutsch 日本語 Português
MetaEditor: plantillas como punto de apoyo

MetaEditor: plantillas como punto de apoyo

MetaTrader 4Ejemplos | 21 diciembre 2015, 12:36
2 801 0
MetaQuotes
MetaQuotes


Dadme un punto de apoyo y moveré el mundo.
Arquímedes

Introducción

El lenguaje de programación MQL4 ofrece un amplio abanico de posibilidades. Estos son algunos ejemplos:

  • archivos de inclusión .mqh. En estos archivos usted puede alojar las funciones y las constantes que necesite, para añadirlas a continuación a su código por medio de la directiva #include.
  • Librerías de funciones que pueden compilarse como programas normales MQL4, y que pueden añadirse al código en tiempo real por medio de la directiva #import.
  • Indicadores personalizados que llevan a cabo cálculos económicos en arrays de series temporales, y que son llamados en tiempo real con la función iCustom().

Otra de las funciones interesantes son las plantillas. Sin embargo, no todos los desarrolladores las conocen, y por tanto no saben que pueden crear Asesores Expertos de forma sencilla y fiable con el Asistente para MQL4. Este artículo explica las ventajas que supone utilizar esta herramienta.


¿Qué es una plantilla?

¿Qué es una plantilla para MetaEditor? Las plantillas (templates, en inglés) son los archivos .mqt que seguramente ya habrá visto en alguna ocasión, y que se almacenan en la carpeta Root_directory_MetaEditor_4/experts/templates/ del terminal.

La siguiente figura muestra diez archivos de ejemplo. Esta es una explicación breve de cada uno de ellos:

  • Expert.mqt - plantilla para crear Asesores Expertos;
  • Script.mqt - plantilla para la creación de scripts;
  • Include.mqt - plantilla para la creación de scripts;
  • indicator.mqt - plantilla para la creación de indicadores;
  • Library.mqt - plantilla para crear librerías.

El resto de templates (Alligator.mqt, etc.) son para crear indicadores según su nombre. Abramos por ejemplo la plantilla Library.mqt con MetaEditor. Para ello tenemos que seleccionar "All files (*.*)" en la lista desplegable del tipo de archivo:



Como podemos observar, el contenido del archivo no es muy grande.
<expert>
type=LIBRARY_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
//+---------------------------------------------------------------------+
//| Mi función                                                          |
//+---------------------------------------------------------------------+
// int MyCalculator(int value,int value2)
//   {
//    return(value+value2);
//   }
//+---------------------------------------------------------------------+

Las tres primeras líneas informan sobre el tipo de archivo al que pertenece la plantilla:


<expert>
type=LIBRARY_ADVISOR
</expert>

Evidentemente, la línea type=LIBRARY_ADVISOR indica a MetaEditor que el archivo actual es una plantilla de librería. MetaEditor utilizará el template necesario de acuerdo a la elección del usuario: EA, indicador personalizado, etc.



A continuación sigue la macrosustitución #header# que, efectivamente, se reemplazará con el nombre que usted eligió en el Asistente de Asesores Expertos.



Por ejemplo, si su EA se llama My_Best_Expert_Advisor, entonces se formarán las siguientes líneas de código en lugar de la macro #header#:


//+---------------------------------------------------------------------+
//|                                          My_Best_Expert_Advisor.mq4 |
//|                         Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+---------------------------------------------------------------------+

En este bloque de comentarios podemos ver el nombre del Asesor Experto, su autor y el enlace correspondiente a la página web. Toda esta información se introduce en los campos correspondientes del Asistente de Asesores Expertos. Estas líneas:

#property copyright "#copyright#"
#property link      "#link#"

contienen las macros #copyright# y #link#, que obviamente se corresponden con los campos del Asistente de Asesores Expertos.


¿Cómo se pone en práctica todo esto?

Principalmente nos interesa insertar nuestro propio código en la plantilla, que luego se generará automáticamente en el momento de crear un EA. Si cuenta con algo de experiencia en la creación de scripts, probablemente sabrá que las secuencias de comandos se lanzan en el gráfico una sola vez. A veces hay que especificar algunos parámetros externos en el algoritmo del script, y otras veces hay que cambiar dichos parámetros en el momento de ejecutarlo. El Asistente de Asesores Expertos no incorpora esta característica de forma predeterminada. Pero podemos alcanzar este objetivo con la siguiente directiva de compilador:

#property show_inputs

Basta con añadir esta línea a cualquier script para que se muestre una ventana de parámetros. Veamos con un ejemplo cómo añadir esta línea automáticamente con el Asistente de Asesores Expertos, en todos y cada uno de los scripts que se crearán. Abramos pues una plantilla EA:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
//+---------------------------------------------------------------------+
//| función de inicio del programa                                      |
//+---------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+---------------------------------------------------------------------+

y añadamos sólo una línea justo antes de la macro que sustituye los parámetros externos (#extern_variables#). Este es el código:

<expert>
type=SCRIPT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#property show_inputs
 
#extern_variables#
//+---------------------------------------------------------------------+
//| función de inicio del programa                                      |
//+---------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+---------------------------------------------------------------------+


A continuación guardaremos los cambios y escribiremos un script con la ayuda del Asistente de Asesores Expertos. Llamaremos a esta secuencia de comandos TestScript, y haremos clic en "Finalizar"


Obtenemos este resultado:
//+---------------------------------------------------------------------+
//|                                                      TestScript.mq4 |
//|                         Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+---------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
//+---------------------------------------------------------------------+
//| función de inicio del programa                                      |
//+---------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+---------------------------------------------------------------------+

El nuevo script difiere en una sola línea de los hechos con la plantilla antigua, pero es de gran ayuda. Ahora no tenemos que escribir la línea:

#property show_inputs

manualmente en los nuevos scripts, puesto que se añade automáticamente. De todos modos, será mucho más fácil comentarla, si no la necesitamos, que volverla a escribir las veces que haga falta. Añadimos manualmente una única línea que contiene el parámetro emptyParam.

//+---------------------------------------------------------------------+
//|                                                      TestScript.mq4 |
//|                         Copyright © 2007, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net/|
//+---------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
extern int emptyParam=1;
//+---------------------------------------------------------------------+
//| función de inicio del programa                                      |
//+---------------------------------------------------------------------+
int start()
  {
//----
   
//----
   return(0);
  }
//+---------------------------------------------------------------------+

Compilemos el código y ejecutémoslo sobre un gráfico. El script no se ejecuta inmediatamente, sino que permite modificar los parámetros externos:



Este es el ejemplo más sencillo que ilustra el uso de las plantillas. La idea consiste en añadir a la plantilla las líneas de código que nos hacen más falta, en el momento de crear un nuevo programa MQL4. De modo que esas líneas de código se añaden automáticamente al ejecutar el Asistente de Asesores Expertos.

Importante. Todas las plantillas se reescriben al instalar el terminal cliente MetaTrader 4. Recuerde hacer las copias de seguridad correspondientes.


¿Dónde nos lleva todo esto?

Gracias a las plantillas prefabricadas podemos escribir Asesores Expertos. ¿Pero cómo ayudan, en realidad, a los desarrolladores principiantes que desean escribir sus propios EAs? Vamos a verlo a continuación con una estrategia basada en la intersección de dos medias móviles. El requisito técnico sencillo para crear un EA con esa estrategia es:

  1. Obtener los valores de las medias móviles de corto y largo plazo.
  2. Comprobar la intersección.
  3. Si la media móvil de corto plazo cruza la otra media, de abajo hacia arriba, entonces hay que comprar, StopLoss=N puntos.
  4. Por el contrario, si la media móvil de corto plazo cruza la otra media de arriba hacia abajo, se produce una señal de venta, StopLoss=N puntos.
  5. Si hay una posición larga abierta, ciérrese con la señal de venta.
  6. Si hay una posición corta abierta, ciérrese con la señal de compra.

Con todo lo anterior el EA estará listo y optimizado. Ahora hay que añadir un parámetro nuevo, TakeProfit = S puntos. Para reemplazar el bloque de alertas de trading utilice los valores del Estocástico en lugar de los del cruce de medias móviles. Y así sucesivamente. En cada caso de uso tendremos que cambiar algo del código, y en algún momento resultará que las cosas no eran tan fáciles como parecían. En un punto determinado nos daremos cuenta que hay que escribir un nuevo Asesor Experto desde el principio, con las nuevas funciones. Los desarrolladores de Asesores Expertos con experiencia utilizan sus propias aproximaciones y prácticas favoritas. Repasemos las técnicas presentadas al comienzo de este artículo:

  • incluir archivos;
  • librerías;
  • indicadores personalizados.

Sin embargo, no producen el máximo resultado sin la plantilla. Lo que nos lleva a la siguiente pregunta. ¿Qué es una plantilla EA? ¿Y por qué es efectiva? Según mi interpretación, una plantilla EA es el prototipo universal de un Asesor Experto real, pero sin características específicas. Es como una superclase con funciones puramente virtuales (en jerga de programación orientada a objetos), o una interfaz (en términos Java). El template describe lo que el EA hace, pero no describe cómo lo hace. Por esto, antes de crear la plantilla, primero hay que analizar la funcionalidad deseada. Primero tenemos que crear la estructura de nuestro Asesor Experto.


Estructura del Asesor Experto

¿Qué se supone que debe hacer un EA? Tras un primer análisis, llegaremos a la conclusión que un EA hace trading, lo que significa que gestiona operaciones, esto es, lleva a cabo solicitudes de apertura y cierre de operaciones. Si es necesario, el Asesor Experto modifica los niveles de StopLoss y TakeProfit de las operaciones abiertas en ese momento, y también coloca o borra órdenes pendientes, actualizando los niveles StopLoss y TakeProfit de dichas transacciones. Podemos esbozar una primera estructura:

  1. Bloque de recepción de señales de trading.
  2. Abrir una orden a mercado o colocar una orden pendiente.
  3. Abrir una orden y borrar órdenes pendientes.

Hemos simplificado la idea general del trading automático en tres subproblemas. En este punto sabemos, por lo tanto, que hay que diseñar una función (bloque 1) para operar sobre la señal actual, que tomará decisiones de compraventa, o bien permanecerá fuera del mercado (sin señal). Entonces sobre la base de la señal de trading (bloque 2) podemos abrir una posición o colocar una orden pendiente. ¿Por qué "puede" y no "debe"? Porque el bloque de las posiciones de apertura tiene en consideración las señales de trading, pero no está obligado a seguirlas ni a abrir posiciones. Supongamos que tenemos algunas posiciones abiertas. En tal caso la apertura de nuevas transacciones expone la cuenta de trading a un riesgo excesivo. Por último pero no por ello menos importante, el bloque 3 del EA, que también es independiente, se encarga de cerrar posiciones y de borrar órdenes pendientes. Las posiciones pueden cerrarse por medio de la señal de trading o teniendo en cuenta variables tales como el tiempo de la posición, el fin de la sesión de trading, etc. Así, la división de la lógica del EA en tres partes tiene sentido.


Mejoras adicionales

Analizando detenidamente este razonamiento llegamos a la conclusión que la estructura anterior no está completa. Tenemos que hacer algunas mejoras adicionales en cada uno de los bloques.

En primer lugar, podemos calcular las señales de trading de los ticks entrantes (un tick representa un cambio en el precio) o realizar estos cálculos solamente en la apertura de las barras nuevas. Por el momento esto no es muy importante, pero tengámoslo presente; de otro modo podemos tener problemas en el futuro si queremos realizar cambios en el código.

También es posible cambiar el marco temporal de las barras. Nuestro EA negociará en gráficos de una hora pero nos puede interesar actualizarlo para que opere en gráficos de cuatro horas. Debemos considerar pues estas variantes.

También unificaremos las señales de trading. Tan solo necesitamos un método sistemático estricto. Como solo tenemos tres tipos de señales de trading, vamos a hacer que nuestro bloque las muestre por medio de constantes, lo que en términos de MQL4 se traduce en lo siguiente:

  • OP_BUY - señal de compra;
  • OP_SELL - señal de venta;
  • OP_BALANCE - sin señal, permanecer fuera del mercado.

En consecuencia, este es el primer bloque:

/**
      1. Señales de trading. Recepción de señales de trading
      a) Todos los ticks                       
      b) Todas las barras del marco temporal     
      OP_BUY      - compra
      OP_SELL     - venta
      OP_BALANCE  - sin señal
*/
Necesitamos un bloque que calcule los niveles importantes para abrir nuevas órdenes o modificar las existentes. Esta funcionalidad no depende de otras partes del código.
/**
      2. 
        a) Calcular SL y TP en toda orden abierta
        b) Calcular OpenPrice, SL, TP, y Lots en toda orden nueva
        c) Calcular OpenPrice, SL y TP en toda orden pendiente
*/

Aquí no mostramos el cálculo del tamaño de la posición, Lots, aunque también es un parámetro. Sin embargo, nos puede interesar definir el tamaño de la nueva posición en otra función, en un bloque separado que llamaremos: bloque de Gestión del Dinero (Money Management).

El siguiente bloque modifica los niveles de las órdenes. También es independiente, y opera sobre los niveles StopLoss y TakeProfit. Además, se pueden modificar en cada tick, o bien en la apertura de una barra nueva. Dotando al EA de esta flexibilidad nos resultará mucho más sencillo optimizarlo en el futuro.

/**
      3. 
        a) Modificación de la orden abierta en cada tick (SL y TP)        
        b) Modificación de la orden pendiente en cada tick (OpenPrice, SL y TP)
        c) Modificación de la orden abierta en las barras nuevas del marco temporal seleccionado (SL y TP)
        d) Modificación de la orden pendiente en las barras nuevas (OpenPrice, SL y TP)
*/

Nótese que especificamos el período de modificación en la apertura de la nueva barra como en el bloque #1 (recepción de señales de trading).

El siguiente bloque es el de cierre de órdenes. Las órdenes se cierran por cuestiones de tiempo o de señal. En este caso nos referimos tanto a la señal de trading recibida, en oposición directa con la posición abierta, así como al cambio en las condiciones que implican la necesidad de mantener la posición.

/**
      4. 
        a) Cierre de la orden por tiempo
        b) Cierre de la orden por señal
*/

He separado el bloque de borrado de órdenes pendientes del bloque de cierre de posiciones porque a veces es mucho más prioritario cerrar una posición abierta; por ejemplo, cuando el precio comienza a moverse muy fuertemente en contra de una posición y en consecuencia debemos cerrarla tan pronto como sea posible. Esto prevalece sobre el borrado de órdenes pendientes. Por cierto, como norma general las órdenes pendientes están en un punto bastante alejado del precio actual.

/**
      5. 
        a) Borrar una orden pendiente por tiempo
        b) Borrar una orden pendiente por condición
*/

El bloque que borra las órdenes es claro, por lo que no necesitamos añadir comentarios adicionales.

Ya para terminar, el último bloque abre posiciones y coloca órdenes pendientes.

/**
      6. 
        a) Abrir una orden a precio de mercado
        b) Colocar una orden pendiente sin fecha y hora de expiración
        c) Colocar una oren pendiente con fecha y hora de expiración
*/

Aunque puede parecer un poco extraño este bloque es el último. Pero si pensamos en ello detenidamente, veremos que esta estructura EA es bastante lógica. Primero de todo resolvemos los preparativos, cerrando todo lo que se tiene que cerrar y eliminando las órdenes pendientes redundantes. Sin preocuparnos por el futuro, sólo entonces abriremos nuevas órdenes y operaremos en el mercado.

/**
      7. 
        a) Abrir una orden a precio de mercado
        b) Colocar una orden pendiente sin fecha y hora de expiración
        c) Colocar una oren pendiente con fecha y hora de expiración
*/

Sin embargo este bloque también contiene algunas variaciones. Se puede abrir una posición a precio de mercado (compra al precio Ask o venta al precio Bid); o colocar una orden pendiente con fecha y hora de vencimiento (el servidor de trading borrará la orden pendiente caducada), o sin fecha de vencimiento hasta la cancelación, es decir, hasta que nosotros mismos borremos la transacción.

Todo lo anterior resulta en la siguiente estructura de plantilla de EA:

<expert>
  type=EXPERT_ADVISOR
</expert>
#header#
#property copyright "#copyright#"
#property link      "#link#"
 
#extern_variables#
 
//+---------------------------------------------------------------------+
//| función de inicialización del experto                               |
//+---------------------------------------------------------------------+
int init()
  {
//----
   
//----
   return(0);
  }
//+---------------------------------------------------------------------+
//| función de desinicialización del experto                            |
//+---------------------------------------------------------------------+
int deinit()
  {
//----
   
//----
   return(0);
  }
//+---------------------------------------------------------------------+
//| función de inicio del experto                                       |
//+---------------------------------------------------------------------+
int start()
  {
//----
 
/**
      1. Señales de trading. Recepción de señales de trading
      a) Todos los ticks                       
      b) Todas las barras del marco temporal     
      OP_BUY      - compra
      OP_SELL     - venta
      OP_BALANCE  - sin señal
*/
 
 
/**
      2. 
        a) Calcular SL y TP en toda orden abierta
        b) Calcular OpenPrice, SL, TP, y Lots en toda orden nueva
        c) Calcular OpenPrice, SL y TP en las órdenes pendientes
*/
 
 
/**
      3. 
        a) Modificación de la orden abierta en cada tick (SL y TP)        
        b) Modificación de la orden pendiente en cada tick (OpenPrice, SL y TP)
        c) Modificación de la orden abierta en las barras nuevas del marco temporal seleccionado (SL y TP)
        d) Modificación de las órdenes pendientes en las barras nuevas (OpenPrice, SL y TP)
*/
 
 
/**
      4. 
        a) Cierre de la orden por tiempo
        b) Cierre de la orden por señal
*/
 
/**
      5. 
        a) Borrar una orden pendiente por tiempo
        b) Borrar una orden pendiente por condición
*/
 
 
/**
      6. 
        a) Abrir una orden a precio de mercado
        b) Colocar una orden pendiente sin fecha y hora de expiración
        c) Colocar una oren pendiente con fecha y hora de expiración
*/
 
//----
   return(0);
  }
//+---------------------------------------------------------------------+

Ahora ya podemos guardar nuestro template. Puede ser de utilidad, ni siquiera contiene funciones adicionales. Es una sencilla descripción textual de los bloques que se necesitan para escribir un EA. El desarrollador de Asesores Expertos es libre de implementar tal o cual variante a partir de esta estructura, incluso puede cambiarla y adaptarla a sus necesidades. En todo caso, al crear un nuevo EA con el asistente, fíjese en la descripción textual de la estructura. Continuemos nuestra exposición en la siguiente sección.


Implementación

A estas alturas podemos comenzar a poblar la estructura de nuestra plantilla EA con funciones genéricas. Estas funciones no describen la implementación final de las operaciones que se llevan a cabo, sino que describen la interacción estándar entre la estructura de la plantilla y las funciones definidas por el usuario. No vamos a desarrollar ninguna función específica para gestionar señales de trading; indicaremos de forma genérica cómo llamar a las funciones, desde el punto de vista del usuario.


Función de aparición de nueva barra en el marco temporal predefinido

Ya hemos tomado la decisión de visualizar las señales de trading en el bloque #1, bien en cada tick, o cuando aparece una barra nueva en un determinado marco temporal predefinido por el usuario. En primer lugar, pues, necesitamos una función que nos informe del evento: "aparece una barra nueva en el marco temporal". Esta función es sencilla:


//+------------------------------------------------------------------------+
//|  Devuelve el signo de aparición de la nueva barra en el marco temporal |
//+------------------------------------------------------------------------+
bool isNewBar(int timeFrame)
   {
   bool res=false;
   
   // el array contiene la hora de apertura de la barra actual (cero)
   // para 7 (siete) marcos temporales
   static datetime _sTime[7];  
   int i=6;
 
   switch (timeFrame) 
      {
      case 1  : i=0; break;
      case 5  : i=2; break;
      case 15 : i=3; break;
      case 30 : i=4; break;
      case 60 : i=5; break;
      case 240: break;
      case 1440:break;
      default:  timeFrame = 1440;
      }
//----
   if (_sTime[i]==0 || _sTime[i]!=iTime(Symbol(),timeFrame,0))
      {
      _sTime[i] = iTime(Symbol(),timeFrame,0);
      res=true;
      }
      
//----
   return(res);   
   }

Establecimiento del marco de tiempo y corrección

La función isNewBar() toma como parámetro el valor del periodo de tiempo, la cantidad de minutos. También presentamos la variable externa TradeSignalBarPeriod para indicar el marco temporal necesario:

extern int  TradeSignalBarPeriod       = 0;

De forma predeterminada es igual a cero. Esto significa que el EA siempre sigue las nuevas barras en su marco temporal, esto es, el marco temporal del gráfico donde se adjunta el mismo. El usuario puede introducir cualquier constante predefinida para seleccionar un período de tiempo. Sin embargo se pueden producir errores. Vamos a añadir una función que compruebe el valor de la variable TradeSignalBarPeriod y, si es necesario, corrija los errores producidos.

//+---------------------------------------------------------------------+
//|  si el marco temporal se especifica incorrectamente, devuelve cero  |
//+---------------------------------------------------------------------+
int getCorrectTimeFrame(int period)
   {
   int res=period;
//----
   switch (period)
      {
      case PERIOD_D1:  break;  // periodo permitido, no hacer nada
      case PERIOD_H4:  break;  // periodo permitido, no hacer nada
      case PERIOD_H1:  break;  // periodo permitido, no hacer nada
      case PERIOD_M30: break;  // periodo permitido, no hacer nada
      case PERIOD_M15: break;  // periodo permitido, no hacer nada
      case PERIOD_M5:  break;  // periodo permitido, no hacer nada
      case PERIOD_M1:  break;  // periodo permitido, no hacer nada
      default: res=Period();   // periodo incorrecto, establecer otro
      }
//----
   return(res);      
   }

Si no se producen errores la función no hace nada. Solamente la vamos a utilizar una vez, justo en el momento de inicializar el EA. En concreto la llamaremos en la función init():

//+---------------------------------------------------------------------+
//| función de inicialización del experto                               |
//+---------------------------------------------------------------------+
int init()
  {
//----
   TradeSignalBarPeriod=getCorrectTimeFrame(TradeSignalBarPeriod);   
   StartMessage(); // recordar los parámetros del EA
//----
   return(0);
  }

Como se puede ver, el bloque init() contiene una nueva función definida por el usuario, StartMessage(). Voy a describirla a continuación desde una perspectiva general. Los Asesores Expertos suelen manejar un montón de parámetros de configuración que con el paso del tiempo uno puede olvidar fácilmente. Pues bien, la función StartMessage() proporciona una breve descripción de los parámetros del Asesor Experto en el momento de su lanzamiento. Dichos parámetros están disponibles en los logs del terminal y en el Probador. Supongo que esta función es de utilidad en cualquier EA, y por eso la incluyo en la plantilla.


Recepción de alertas de trading

Echemos un vistazo al bloque que genera alertas de trading. Y sigamos analizando diferentes escenarios. Tras finalizar nuestro Asesor Experto nos puede interesar comprobar cómo funciona en determinados días de la semana. Quizás solo queramos hacer trading los miércoles, por poner solo un ejemplo, y nuestro EA deba permanecer silencioso el resto de la semana. Vamos a añadir la capacidad de elegir los días de trading por adelantado. Para ello creamos la variable externa TradeDay, de tipo entero. Si vale cero (Sunday=0, Monday=1, etc.) hacemos trading como siempre. Por el contrario, si es diferente de cero, solamente hacemos trading durante el día de la semana especificado.

También nos interesa la operación inversa. En cuyo caso especificamos el día de la semana que el EA tiene prohibido operar. Necesitamos crear la variable externa ReversDay. El valor 'false' establece el modo de funcionamiento normal (TradeDay puntos en el día de trading). En caso contrario, si vale 'true', las condiciones se invierten y TradeDay apunta al día de la semana donde no se opera. Por ejemplo, TradeDay=5 y ReversDay=true significa que los viernes no operamos (Friday=5).

Este sistema de trading se puede invertir. Por ejemplo, podemos invertir por completo nuestra estrategia en el futuro, y ver qué sucede, en cuyo caso hay que cambiar la compra por la venta y viceversa. Es interesante ofrecer esta posibilidad. Para ello basta con introducir otra variable lógica externa: ReversTradeSignal. Si vale 'false' (valor predeterminado), se pone en funcionamiento la estrategia inicial. En caso contrario, si se establece al valor 'true', el sistema se invierte. Las variables externas añadidas tienen este aspecto:

// constante "fuera del mercado"
#define OP_BALANCE 6
 
// configuración del día de trading
extern int  TradeDay                   = 0; 
extern bool ReversDay                  = false;
 
// reverso del sistema de trading
extern bool ReversTradeSignal          = false;
 
//  configuración de la frecuencia de trading
extern bool TradeSignalEveryTick       = false;
extern int  TradeSignalBarPeriod       = 0;

He añadido la constante OP_BALANCE para indicar la ausencia de señal de trading. Obsérvese la variable TradeSignalEveryTick. Si vale true quiere decir que se reciben señales en cada tick. Como se puede ver, no hemos escrito ninguna línea de código de nuestro sistema de trading; simplemente hemos presentado algunas variables cuya razón de ser puede olvidarse fácilmente después de un tiempo. Por ello escribimos la función informativa StartMessage():

//+---------------------------------------------------------------------+
//| Muestra un mensaje sobra la configuración del EA                    |
//+---------------------------------------------------------------------+
void StartMessage()
   {
   int i=0;
   string currString="";
   string array[3];
//----
   array[0]=StringConcatenate("El Asesor Experto ",WindowExpertName()," tiene esta configuración:");
 
   if (TradeSignalEveryTick)
      array[1]="1) se tienen en cuenta las señales de trading en cada tick; ";
   else       
      array[1]=StringConcatenate("1) se tienen en cuenta las señales de trading en cada barra con periodo ",
                        TradeSignalBarPeriod," minutos;");
 
   if (TradeDay==0)   // el día de trading no se especifica
      array[2]="2) se permite hacer trading todos los días de la semana; ";
   else 
      {
      if (ReversDay) // no se permite hacer trading en el día especificado
         array[2]=StringConcatenate("2) se permite hacer trading todos los días a excepción del día ",TradeDay);
      else           // solo se permite hacer trading el día de la semana especificado
         array[2]=StringConcatenate("2) solo se permite hacer trading el día número ",TradeDay);
      }
   for ( i=0;i<3;i++) currString=StringConcatenate(currString,"\n",array[i]);
   Comment(currString);
 
   for (i=2;i>=0;i--) Print(array[i]);
   
//----
   return;
   }

Esta función muestra el nombre del EA junto con una información resumida de su configuración. Usted puede escribir sus propias líneas en 'array[]' en el momento de escribir su propio Asesor Experto.

Llegados a este punto podemos crear una función que calcule una señal de trading. La función recibe esta configuración:

  • calcular una señal de trading en cada tick o en la apertura de una nueva barra del período de tiempo dado;
  • revertir o no revertir una señal;
  • tomar en consideración el día de la semana, o no tenerlo en cuenta;
  • convertir los días de trading en días de no operación, o no convertir dichos días.

Obsérvese esta línea de código:

//+---------------------------------------------------------------------+
//| recibir una señal de trading                                        |
//+---------------------------------------------------------------------+
// tradeDay - día de la semana donde operamos; si es igual a cero operamos todos los días
//
// useReversTradeDay - si es igual a 'true', los días de trading se transforman en días donde no se opera
//
// everyTick  - si es igual a cero, la función calcula una señal en cada tick
//
// period - if everyTick==false, entonces se calcula en cuanto aparece una barra nueva con ese periodo
int getTradeSignal(int tradeDay,          // generalmente igual a 0
                   bool useReversTradeDay,// generalmente igual a 'false'
                   bool everyTick,        // la señal se calcula en cada tick
                    int period            // periodo de trabajo de los indicadores y de las señales
                   )
   {
   int signal=OP_BALANCE;
//----
   if (tradeDay!=0)  // consideraremos los días de la semana
      {
      // día donde no operamos
      if (useReversTradeDay && tradeDay==DayOfWeek()) return(signal);
      
      // operamos todos los días excepto el día igual a tradeDay 
      if (!useReversTradeDay && tradeDay!=DayOfWeek()) return(signal);
 
      }
 
   if (!everyTick) // si no tomamos señales de trading en cada tick
      { // ni tenemos ninguna barra nueva en el marco temporal de 'period' minutos,
      if (!isNewBar(period)) return(signal); // entonces salimos con una señal vacía
      }
 
// Completar la función yourFunction() con su propio código/algoritmo
   signal=yourFunction(period);
 
 
   if (signal!=OP_BUY && signal!=OP_SELL) signal = OP_BALANCE;
   
//----
   return(signal);   
   }
   signal=yourFunction();

Al diseñar nuestra propia estrategia escribiremos nuestra función (yourFunction() en el ejemplo), que tiene que devolver uno de estos valores:

  • OP_BUY - compra
  • OP_SELL - venta
  • OP_BALANCE - sin señal

Bloque de identificación de órdenes "amigas o rivales"

Otra de las dificultades que presenta el desarrollo de Asesores Expertos es la detección de los tickets de las órdenes con que va a trabajar el EA. Este problema se suele solucionar mediante ciclos for(), donde los tickets se seleccionan con la función OrderSelect(). Sin embargo, tenga en cuenta que posiblemente necesitará detectar órdenes "amigas" en más de una ocasión. En algunos casos habrá que modificar el StopLoss. Y en otros casos tendremos que cerrar algunas órdenes. En dichas ocasiones tal vez necesitemos borrar algunas órdenes pendientes.

Para procesar las órdenes "amigas" estándar, hemos decidido llenar el array de órdenes "amigas" con todos los parámetros necesarios, cada vez al inicio de la función start(). Por todo ello añadimos dos arrays a nivel global:

double Tickets[][9];// array que almacena información de las órdenes "amigas":
// Tickets[][0] - número de ticket
// Tickets[][1] - tipo de orden
// Tickets[][2] - lotes
// Tickets[][3] - precio de apertura
// Tickets[][4] - stop loss
// Tickets[][5] - take profit
// Tickets[][6] - número mágico
// Tickets[][7] - hora de expiración
// Tickets[][8] - hora de apertura
// 
 
string CommentsTicket[1000][2];         // array que almacena los símbolos y los comentarios de las órdenes. 1000 líneas son suficientes
// CommentsTicket[][0] - nombre del símbolo
// CommentsTicket[][1] - comentario de la orden

La función que llena estos arrays es bastante sencilla:

//+---------------------------------------------------------------------+
//| Preparación del array de órdenes "amigas"                           |
//+---------------------------------------------------------------------+
void PrepareTickets(double & arrayTickets[][9], string & comm[][2],int MN)
   {
   int count=0;   // contador de relleno
   
   // hagamos que el tamaño del array no asigne memoria cada vez
   ArrayResize(arrayTickets,20);
//----
   int total=OrdersTotal();
   for (int i=0;i<total;i++)
      {
      bool ourTicket=false;
      if (OrderSelect(i,SELECT_BY_POS))
         {
         if (!isOurOrder(OrderTicket()))
            {// si la función especial no detecta la orden como "amiga",
            // llevar a cabo las comprobaciones normales
 
            // comprobar Symbol
            if (OrderSymbol()!= Symbol()) continue;
         
            //  otras comprobaciones...
            // ....
         
            // última comprobación, verificar MagicNumber
            if (OrderMagicNumber()!=ExpertMagicNumber) continue;
         
            }
         // como no hemos parado en ningún sitio, esta es una orden "amiga"
         //  llenar el array
         arrayTickets[count][0] = OrderTicket();
         arrayTickets[count][1] = OrderType();
         arrayTickets[count][2] = OrderLots();
         arrayTickets[count][3] = OrderOpenPrice();
         arrayTickets[count][4] = OrderStopLoss();
         arrayTickets[count][5] = OrderTakeProfit();
         arrayTickets[count][6] = OrderMagicNumber();
         arrayTickets[count][7] = OrderExpiration();
         arrayTickets[count][8] = OrderOpenTime();
 
         comm[count][0] = OrderSymbol();
         comm[count][1] = OrderComment();
         // incrementar el contador de órdenes "amigas"
         count++;
         }
      }
   
   // truncamos el tamaño de los arrays al mínimo 
   ArrayResize(arrayTickets,count);
   ArrayResize(comm,count);
 
//----
   return;   
   }

A modo de ejemplo ofrezco una descripción mínima de los criterios que permiten distinguir las órdenes "amigas" de las "rivales", en una función general, utilizando las funciones OrderMagicNumber() y OrderSymbol(). Al buscar en todas las órdenes omitimos las "rivales" y llenamos los arrays con la información de las "amigas". Si desea que la función sea más flexible, no dude en crear y llamar a otra función definida por el usuario. En tal caso se pueden describir más criterios que detecten si una orden determinada es "amiga" o "rival". Por eso he añadido la comprobación de variables globales. Si hay una variable global con el nombre de nuestro EA con valor igual al ticket de una orden determinada, dicha orden también será vista como "amiga". Esto es útil si, por ejemplo, la posición se ha abierto manualmente y ahora queremos que sea procesada por nuestro EA.

//+-----------------------------------------------------------------------+
//| Devuelve 'true' si hay disponible una variable global con este nombre |
//+-----------------------------------------------------------------------+
bool isOurOrder(int ticket)
   {
   bool res=false;
   
   // en modo pruebas no utilizamos variables globales!
   if (IsTesting()) return(true);// devolver inmediatamente el resultado positivo
 
   int temp;
//----
   for (int i=0;i<5;i++)
      {
      if (GlobalVariableCheck(WindowExpertName()+"_ticket_"+i))
         {// existe tal variable global
            temp=GlobalVariableGet(WindowExpertName()+"_ticket_"+i);
            if (temp==ticket)
               { // se encontró una variable global con un valor igual a 'ticket'
               res=true;  // de modo que es una orden "amiga"
               break;
               }
         }
      }
//----
   return(res);   
   }

En este punto ya hemos realizado un gran número de preparativos. Ahora la función start() tiene este aspecto:

//+---------------------------------------------------------------------+
//| función de inicio del experto                                       |
//+---------------------------------------------------------------------+
int start()
  {
   // actualizar el tamaño del array antes de utilizarlo por primera vez
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtener los arrays de las órdenes "amigas"
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Señales de trading. Recepción de señales de trading
      a) Todos los ticks                                 (TradeSignalEveryTick=true)
      b) Barras del periodo predeterminado               (TradeSignalBarPeriod=...)
      OP_BUY      - compra
      OP_SELL     - venta
      OP_BALANCE  - sin señal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
/**
      2. 
        a) Calcular SL y TP en toda orden abierta
        b) Calcular OpenPrice, SL, TP y Lots en la orden nueva
        c) Calcular OpenPrice, SL y TP en toda orden pendiente
*/

Cálculo de los niveles Stop Loss y Take Profit de las órdenes existentes

A estas alturas estamos preparados para adentrarnos en la siguiente etapa. Hemos calculado un array bidimensional de órdenes "amigas", Tickets[][9]. Pues bien, ahora vamos a calcular los niveles SL y TP, conociendo las características de cada una de las órdenes. Los nuevos valores calculados tienen que almacenarse en algún sitio, así que, con este objetivo, tenemos que crear un array global más:

double newSL_and_TP[][5];// array que almacena los valores nuevos de SL y TP
// newSL_and_TP[][0] - número de ticket a controlar
// newSL_and_TP[][1] - nuevos valores de SL
// newSL_and_TP[][2] - nuevos valores de TP
// newSL_and_TP[][3] - nuevo precio de apertura (para órdenes pendientes)
// newSL_and_TP[][4] - 0 para órdenes abiertas y 1 para órdenes pendientes

Quizás el siguiente parámetro requiere alguna explicación:

newSL_and_TP[][4] - 0 for open orders and 1 for pending orders

Necesitamos este parámetro para modificar las órdenes de mercado y las órdenes pendientes en funciones separadas. Pues bien, vamos a crear una función que toma el array Ticketsp[][9] y el signo del sistema inverso y llena de valores el nuevo array newSL_and_TP[][5] (la escala de la segunda dimensión aparece en paréntesis). La llamada a esta función es así:

   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);

El primer parámetro, ReversTradeSignal, puede tomar los valores 'true' (sistema invertido) o 'false'. El segundo parámetro es el array de órdenes "amigas" (si no hay órdenes su tamaño es igual a cero). El tercer parámetro es un array que se completará con esta función. La función se describe a continuación:

//+---------------------------------------------------------------------+
//|  Crea un array con los nuevos valores de SL y TP                    |
//+---------------------------------------------------------------------+
void CalculateSL_and_TP(bool ReversTrade,       // sistema inverso 
                      double arrayTickets[][9], // array de órdenes "amigas"
                      double &amp; arraySL_TP[][5]  // valores nuevos de SL, TP y openPrice
                      )
   {
   // en primer lugar, establecemos a cero el array obtenido!
   ArrayResize(arraySL_TP,0);
   // si el array de órdenes está vacío, entonces salimos   
   int    i,size=ArrayRange(arrayTickets,0);
   if (size==0) return;
//----
   int    sizeSL_TP=0;
   double lots, openPrice, SL,TP;
   double newSL,newTP, newOpen;
   double oldOpenPrice, oldSL,oldTP;    
 
   int type,ticket,oldType;
   for (i=0;i>size;i++)
      {
      ticket    = arrayTickets[i][0]; // número de ticket
      type      = arrayTickets[i][1]; // tipo de orden
      lots      = arrayTickets[i][2]; // volumen de la orden
      openPrice = arrayTickets[i][3]; // precio de apertura de la orden
      SL        = arrayTickets[i][4]; // Stop Loss
      TP        = arrayTickets[i][5]; // Take Profit
      
      if (ReversTrade) // invertir todos los niveles teniendo en cuenta el spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Tipo de orden no válido Type=",type," en la función CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // sin invertir el sistema
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // si es una orden pendiente, obtener un precio nuevo de apertura
      if (type<OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL<0 || newTP<0 || newOpen<0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL<0) arraySL_TP[sizeSL_TP][1] = newSL; // nivel nuevo de SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP<0) arraySL_TP[sizeSL_TP][2] = newTP; // nivel nuevo de TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen<0)arraySL_TP[sizeSL_TP][3] = newOpen; // precio nuevo de apertura
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type<OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // orden de mercado
            else           arraySL_TP[sizeSL_TP][4]=0; // orden de mercado
         }
      }         
//----
   return;
   }

Tal y como se observa, al principio de todo establecemos a cero el tamaño del array que contiene los valores nuevos de SL y TP. A continuación tenemos que calcular el tamaño del array de las órdenes "amigas".

   int   size=ArrayRange(arrayTickets,0);
   // si el array de órdenes está vacío, entonces salimos   
   if (size==0) return;

Si no hay órdenes entonces ya hemos terminado, así que acabamos con una salida representada por el array arraySL_TP[][] de tamaño cero. Los arrays de tamaño cero implican que no existen instrucciones sobre dichos arrays. Entonces organizamos un ciclo que procesa las órdenes:

 for (i=0;i<size;i++)
      {
      ticket    = arrayTickets[i][0]; // número de ticket
      type      = arrayTickets[i][1]; // tipo de orden
      lots      = arrayTickets[i][2]; // volumen de la orden
      openPrice = arrayTickets[i][3]; // precio de apertura de la orden
      SL        = arrayTickets[i][4]; // Stop Loss
      TP        = arrayTickets[i][5]; // Take Profit
      
      oldOpenPrice = openPrice;
      oldSL = SL;
      oldTP = TP;
         
      
      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
      
      // si es una orden pendiente, recibimos un precio nuevo de apertura
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);
      
      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket  
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // nuevo nivel SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // nuevo nivel TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // nuevo precio de apertura
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // orden de mercado
            else           arraySL_TP[sizeSL_TP][4]=0; // orden de mercado
         }
      }

Calculamos los valores actuales de oldSL, oldTP y oldOpenPrice, porque podemos cambiar los niveles de apertura de las órdenes pendientes, y pasamos estos valores como parámetros a las funciones para calcular los nuevos valores de SL, TP y OpenPrice.

      newSL  = getNewSL(oldType,lots,oldOpenPrice,oldSL,oldTP);
      newTP  = getNewTP(oldType,lots,oldOpenPrice,oldSL,oldTP);
     
      // si es una orden pendiente, obtenemos un nuevo precio de apertura
      if (type>OP_SELL) newOpen=getNewOpenPricePending(type,lots,openPrice,oldSL,oldTP);

Entonces tan solo hay que aumentar en uno el tamaño del array de nuevos valores SL, TP y OpenPrice, si, por lo menos, uno de estos niveles es diferente de cero; esto es, la orden se tiene que modificar.

      if (newSL>0 || newTP>0 || newOpen>0)
         {
         sizeSL_TP=ArrayRange(arraySL_TP,0);
         arraySL_TP[sizeSL_TP][0] = arrayTickets[i][0]; // ticket   
         if (newSL>0) arraySL_TP[sizeSL_TP][1] = newSL; // nuevo nivel de SL
            else      arraySL_TP[sizeSL_TP][1] = arrayTickets[i][4];
            
         if (newTP>0) arraySL_TP[sizeSL_TP][2] = newTP; // nuevo nivel TP
            else      arraySL_TP[sizeSL_TP][2] = arrayTickets[i][5];
         if (newOpen>0)arraySL_TP[sizeSL_TP][3] = newOpen; // nuevo precio de apertura
            else       arraySL_TP[sizeSL_TP][3] = arrayTickets[i][3];
         if (type>OP_SELL) arraySL_TP[sizeSL_TP][4]=1; // orden de mercado
            else           arraySL_TP[sizeSL_TP][4]=0; // orden de mercado
         }
      }

Ahora vamos a analizar la posibilidad de revertir el sistema de trading. ¿Qué significa una inversión? Tan sencillo como que la compra se convierte en venta, y viceversa. El precio de apertura se cambia por el valor del spread, ya que compramos a precio Ask y vendemos a precio Bid. Además, los niveles SL y TP se transponen, de nuevo, considerando el spread. Veámoslo en el siguiente código:

      if (ReversTrade) // invertir los niveles considerando el spread
         {
         switch (type)
            {
            case OP_BUY      : oldType = OP_SELL     ; break;
            case OP_SELL     : oldType = OP_BUY      ; break;
            case OP_BUYLIMIT : oldType = OP_SELLSTOP ; break;
            case OP_SELLLIMIT: oldType = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : oldType = OP_SELLLIMIT; break;
            case OP_SELLSTOP : oldType = OP_BUYLIMIT ; break;
            default: Print("Tipo de orden no válido Type=",type," en la función CalculateSL_and_TP()!!!");                           
            }
 
         double temp;
         int spread = MarketInfo(Symbol(),MODE_SPREAD);
         int digits = MarketInfo(Symbol(),MODE_DIGITS);
         if (type==OP_BUY || type==OP_BUYSTOP || type==OP_BUYLIMIT)  
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP+Point*spread,digits);
               else oldSL=0;
               
            if (SL!=0) oldTP = NormalizeDouble(temp+Point*spread,digits);
               else oldTP=0;
               
            oldOpenPrice = NormalizeDouble(openPrice - Point*spread,digits);
            }
         if (type==OP_SELL) 
            {
            temp = SL;
            if (TP!=0) oldSL = NormalizeDouble(TP-Point*spread,digits);
               else oldSL=0;
            
            if (SL!=0) oldTP = NormalizeDouble(temp-Point*spread,digits);
               else oldTP=0;
            
            oldOpenPrice = NormalizeDouble(openPrice + Point*spread,digits);
            }
         }
      else   // sin invertir el sistema
         {
         oldOpenPrice = openPrice;
         oldSL = SL;
         oldTP = TP;
         }

Dado que la función CalculateSL_and_TP() es extensa, he decidido no proporcionar aquí el código completo de los cambios realizados. Por favor, consulte el código correspondiente en el archivo Template_EA.mqt. Finalmente llegados a este punto podemos decir que hemos solucionado el problema del cálculo de los nuevos valores Stop Loss, Take Profit y OpenPrice. Solo queda crear las funciones que se llaman desde este bloque de cálculos. Estas son funciones vacías que se completan en el momento de crear un EA específico. Nosotros les hemos puesto el nombre de "funciones molde".


Funciones molde

A continuación se muestran las funciones:

//+---------------------------------------------------------------------+
//|  Calcula el nivel de Stop Loss                                      |
//+---------------------------------------------------------------------+
double getNewSL(int    type,      // tipo de orden
                double lots,      // volumen, puede ser útil
                double openPrice, // precio de apertura
                double stopLoss,  // nivel actual de Stop Loss
                double takeProfit // nivel actual de Take Profit
                )
   {
   double res=-1;
//----
//  este es el código de los cálculos del Stop Loss
//----
   return(res);   
   }
 
//+---------------------------------------------------------------------+
//|  Calcula el nivel de Take Profit                                    |
//+---------------------------------------------------------------------+
double getNewTP(int    type,      // tipo de orden
                double lots,      // volumen, puede ser útil
                double openPrice, // precio de apertura
                double stopLoss,  // nivel actual de Stop Loss
                double takeProfit // nivel actual de Take Profit
                )
   {
   double res=-1;
//----
//  este es el código de los cálculos del Take Profit
//----
   return(res);   
   }
 
//+---------------------------------------------------------------------+
//|  Calcula en nuevo precio de apertura de la orden pendiente          |
//+---------------------------------------------------------------------+
double getNewOpenPricePending(int    type,      // tipo de orden
                              double lots,      // volumen, puede ser útil
                              double openPrice, // precio de apertura
                              double stopLoss,  // nivel actual de Stop Loss
                              double takeProfit // nivel actual de Take Profit
                              )
   {
   double res=-1;
//----
//  este es el código de los cálculos del precio de apertura
 
//----
   return(res);   
   }

Estas tres funciones son muy parecidas entre sí; cada una de ellas obtiene las mismas entradas y devuelve un valor de tipo double mediante la variable res. Esta variable se inicializa inmediatamente con un valor negativo cuyo valor se devolverá, si no lo actualizamos nosotros con nuestro propio código. Esto implica la ausencia del nivel calculado, ya que el precio es siempre positivo.

Por otra parte, aquí también podemos escribir la función yourFunction() que se llamará en el bloque que calcula las señales de trading, desde la función getTradeSignal():

//+---------------------------------------------------------------------+
//|  Función que produce señales de trading                             |
//+---------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
// (aquí tiene que haber un código que produzca señales de trading con el marco temporal workPeriod)
//----
   return (res);   
   }

Esta función obtiene workPeriod, el marco temporal a utilizar en llamada de indicadores personalizados o estándar. La variable de retorno res se inicializa con el valor de OP_BALANCE. Este es el valor que devuelve la función, a no ser que insertemos nuestro propio código que modifique dicha variable. Lo que se interpreta como que no hay señal de trading.

Ya hemos terminado; no hay más funciones molde en esta plantilla. Si usted ha seguido este tutorial hasta aquí le resultará sencillo insertar las funciones que necesite en las distintas partes del código.


Cálculo de parámetros de una orden nueva

Ahora vamos a obtener los valores SL, TP, volumen, y otros parámetros, necesarios para colocar una orden o una orden pendiente. Los nombres de las siguientes variables son autoexplicativos.

   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL, pendingTP, pendingComment);

Estas variables toman sus valores de dos funciones: una que calcula los valores de una orden de mercado y otra que hace lo mismo en una orden pendiente. Esta separación nos permite escribir nuestro código de una manera más sencilla, y también facilita realizar posibles cambios futuros. Esta es la función CalculateNewMarketValues():


//+---------------------------------------------------------------------+
//|  Calcula los datos para abrir una orden nueva                       |
//+---------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // si no hay señal de trading, salimos
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // esto quiere decir que no abriremos nada
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  inserte aquí su propio código para calcular todos los parámetros
//----
   return;   
   }

La función recibe la señal de trading actual, trSignal, y calcula los parámetros a devolver.

Por favor, observe que los parámetros tienen que inicializarse con valores seguros.

Si no insertamos nuestro propio código, la variable marketType tendrá el valor -1 (menos uno) lo que significa que no hay ninguna intención de abrir una orden. Recuerde que las constantes de las operaciones de trading tienen valores no negativos. La función que calcula los parámetros de apertura de una orden pendiente es muy parecida:

//+---------------------------------------------------------------------+
//|  Calcula los datos para colocar una orden pendiente                 |
//+---------------------------------------------------------------------+
void CalculateNewPendingValues(int    trSignal, 
                               int    & pendingType, 
                               double & pendingOpenPrice, 
                               double & pendingLots, 
                               double & pendingSL, 
                               double & pendingTP, 
                               string & pendingComment)
   {
   // si no hay señal de trading, salir
   if (trSignal==OP_BALANCE) return;
 
   pendingType      = -1; 
   pendingOpenPrice = 0; 
   pendingLots      = 0; 
   pendingSL        = 0; 
   pendingTP        = 0; 
   pendingComment   = 0;
//----
   // inserte aquí su propio código para calcular los parámetros
//----
   return;
   }

La única diferencia es que calcula un parámetro más: el precio de apertura de la orden pendiente.


Modificación de órdenes

La siguiente operación del EA es la modificación de las órdenes "amigas". Se utilizan dos funciones para este fin.

   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);

Son casi iguales, cada una toma los siguientes parámetros de entrada:

  • ReversTradeSignal - indica que el sistema de trading está invertido;
  • ModifyMarketOrderEveryTick o ModifyPendingEveryTick - modificación de orden en cada tick;
  • ModifyMarketBarPeriod o ModifyPendingBarPeriod - marco temporal en minutos donde se realiza la modificación, si no hace falta cambiar los niveles del precio en cada tick;
  • array newSL_and_TP[][5] - contiene los tickets de las órdenes a modificar, y los nuevos valores SL, TP (y OpenPrice, en el caso de las órdenes pendientes).

Echemos un vistazo a la primera función, ModifyMarkets():

//+---------------------------------------------------------------------+
//|  Modificación de las órdenes de mercado                             |
//+---------------------------------------------------------------------+
void  ModifyMarkets(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // no cambiamos nada, salir
 
   bool res;
//----
   if (!ModifyEveryTick )// si la modificación en cada tick está prohibida
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no aparece ninguna barra nueva
      }
 
   if (!Revers) // trabajo directo
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // no es una orden de mercado, saltar 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// la modificación ha fallado
            {
            Print("Fallo al modificar la orden #",ticket,". Error ",GetLastError());
            // procesamiento posterior de esta situación
            }
         }
       }  
   else  // el sistema de trading se revierte, transponer SL y TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type!=0) continue; // no es una orden de mercado, saltar 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// la modificación ha fallado
            {
            Print("Fallo al modificar la orden #",ticket,". Error ",GetLastError());
            // procesamiento posterior de la situación
            }
         }
      }         
 
//----
   return;   
   }

La primera comprobación ya es un estándar: verificar el tamaño cero del array newSL_and_TP[][] para su modificación. A continuación se comprueba la necesidad de modificación en cada tick. Si no hay necesidad de ello (ModifyEveryTick=false) se comprueba la aparición de una barra nueva en el marco temporal de ModifyBarPeriod minutos. Y si la comprobación no se satisface salimos sin hacer nada:


   if (!ModifyEveryTick )// la modificación está prohibida
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no aparece ninguna barra nueva
      }

Sin embargo, si las comprobaciones preliminares se ejecutan con éxito podemos comenzar a modificar las órdenes. Sin olvidar, al mismo tiempo, los dos modos de funcionamiento del sistema: directo e inverso.

   if (!Revers) // modo directo
      {
      for (i=0;i<size;i++)
         {
         //  código de modificación de la orden del sistema directo
         }
       }  
   else  // el sistema de trading se invierte, transponer SL y TP 
      {
      for (i=0;i<size;i++)
         {
         //  código de modificación de la orden del sistema inverso
         }
      }

La única diferencia entre las dos es que los valores de newSL_and_TP[i][1] y newSL_and_TP[i][2] (StopLoss and TakeProfit) se transponen en la función OrderSend().

La función que modifica las órdenes pendientes es casi igual, simplemente añadimos el precio de apertura de la orden pendiente:

//+---------------------------------------------------------------------+
//|  Modificaciones de las órdenes de mercado                           |
//+---------------------------------------------------------------------+
void  ModifyPendings(bool Revers,
                    bool ModifyEveryTick,
                    int  ModifyBarPeriod,
                    double newSL_and_TP[][])
   {
   int i,type,ticket,size=ArrayRange(newSL_and_TP,0);
   if (size==0) return;  // no hay que modificar nada, salimos
 
   bool res;
//----
   if (!ModifyEveryTick )// la modificación está prohibida
      {   
      if (!isNewBar(ModifyBarPeriod)) return;   // no aparece ninguna barra nueva
      }
 
   if (!Revers) // modo directo
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // no es una orden pendiente, saltamos
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][1],newSL_and_TP[i][2],0);
         if (!res)// fallo en la modificación
            {
            Print("Fallo al modificar la orden #",ticket,". Error ",GetLastError());
            // procesamiento posterior de la situación
            }
         }
       }  
   else  // el sistema de trading se invierte, transponer SL y TP 
      {
      for (i=0;i<size;i++)
         {
         type=newSL_and_TP[i][4];
         if (type==0) continue; // no es una orden pendiente, saltamos 
         ticket=newSL_and_TP[i][0];
         res=OrderModify(ticket,newSL_and_TP[i][3],newSL_and_TP[i][2],newSL_and_TP[i][1],0);
         if (!res)// fallo en la modificación
            {
            Print("Fallo al modificar la orden #",ticket,". Error ",GetLastError());
            // procesamiento posterior de la situación
            }
         }
      }         
 
//----
   return;   
   }

Me gustaría remarcar que el tipo de orden se comprueba en ambas funciones:

         type=newSL_and_TP[i][4];

Según el valor de la variable 'type' (0 o 1), el programa decide si el ticket se ignora o se procesa. Hasta aquí lo relativo a las funciones que modifican órdenes.


Cierre de órdenes de mercado

Ahora tenemos que codificar el cierre de órdenes de mercado. Para ello, utilizamos dos arrays con información sobre las órdenes que se van a cerrar, así como dos funciones que trabajan con dichos arrays:

/**
      4. 
        a) Cerrar una orden abierta por hora
        b) Cerrar una orden abierta por señal
*/
   // tickets de las órdenes a cerrar
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // establecemos a cero el tamaño de los arrays
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // preparamos un array de ticket para el cierre
   if (trSignal!=OP_BALANCE) P
        repareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // cerrar las órdenes especificadas
   CloseMarketOrders(ticketsToClose,lotsToClose);

El array ticketsToClose[][2] almacena los valores del ticket y el tipo de orden a cerrar, mientras que lotsToClose[] contiene información sobre la cantidad de lotes a cerrar en cada posición. La función PrepareTicketsToClose() recibe como entrada el array de las órdenes, Tickets[][], y el valor de la señal de trading actual. Las señales de compra también sirven como instrucciones para cerrar órdenes de venta. La misma función PrepareTicketsToClose() se ha escrito con un volumen menor.

Añada sus propias condiciones para incluir tal o cual orden a la lista de órdenes que se tienen que cerrar. Si el tamaño del array de entrada arrayTickets es cero (es decir, no hay órdenes) salga pronto de la función, como es habitual.

//+---------------------------------------------------------------------+
//|  Preparar un array de tickets para cerrar órdenes                   |
//+---------------------------------------------------------------------+
void PrepareTicketsToClose(int signal, bool Revers, int & ticketsClose[][2], 
                                   double & lots[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
 
   int i,type,ticket,closeSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // si no es una orden de mercado, saltar
      if (type>OP_SELL) continue;
 
      if (Revers) // revertir el tipo de orden
         {
         if (type==OP_BUY) type=OP_SELL; else type=OP_BUY;
         }
      
      // aquí decidimos el destino de la orden abierta
      // si la dejamos en el mercado o la añadimos al array de órdenes a cerrar
      if (type==OP_BUY)
         {
         //  
         // código que nos permite conservar la orden de compra
         
         // por ejemplo
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELL)
         {
         //  
         // código que nos permite conservar la orden de venta
         
         // por ejemplo
         if (signal==OP_SELL) continue;
         }
 
      closeSize=ArrayRange(ticketsClose,0);
      ArrayResize(ticketsClose,closeSize+1);
      ArrayResize(lots,closeSize+1);
      ticketsClose[closeSize][0] = arrayTickets[i][0]; // ticket #
      ticketsClose[closeSize][1] = arrayTickets[i][1]; // tipo de orden
      Print("arrayTickets[i][0]=",arrayTickets[i][0],"   ticketsClose[closeSize][0]=",ticketsClose[closeSize][0]);
      
      // aquí especificamos la cantidad de lotes a cerrar
      lots[closeSize] = arrayTickets[i][2]; // volumen a cerrar
      // se pueden cerrar parcialmente, en cuyo caso hay que reescribir la línea de código anterior
      }
//----
   return;   
   }

La función CloseMarketOrders() no representa ninguna dificultad:

//+---------------------------------------------------------------------+
//|  Cierra órdenes con los tickets especificados                       |
//+---------------------------------------------------------------------+
void CloseMarketOrders(int ticketsArray[][2], double lotsArray[])
   {  
//----
   int i,size=ArrayRange(ticketsArray,0);
   if (size==0) return;
   
   int ticket,type;
   double lots;
   bool res;
   
   int total=OrdersTotal(); 
   Print("",size," órdenes deberían cerrarse, órdenes abiertas:",total);
   
   for (i=0;i<size;i++)
      {
      ticket = ticketsArray[i][0];
      type   = ticketsArray[i][1];
      lots   = lotsArray[i];
      Print("Cerrar orden #",ticket," tipo=",type," ",lots," lotes" );
      Print(" ");
      RefreshRates(); // por si acaso, actualizar los datos del entorno del mercado
 
      // bloque de cierre de compras
      if (type==OP_BUY)
         {
         res = OrderClose(ticket,lots,Bid,Slippage,Orange);
         if (!res)
            {
            Print("Fallo al cerrar orden de compra #",ticket,"!  Error #",GetLastError());
            //  procesamiento del error, código independiente
            }
         }
 
      //  bloque de cierre de ventas
      if (type==OP_SELL)
         {
         res = OrderClose(ticket,lots,Ask,Slippage,Orange);
         if (!res)
            {
            Print("Fallo al cerrar orden de venta #",ticket,"!  Error #",GetLastError());
            //  procesamiento del error, código independiente
            }
         }
 
      } 
//----
   return;
   }

El bucle itera sobre el array de cierre, la función RefreshRates() actualiza el entorno del mercado, y el programa intenta cerrar la transacción al precio correspondiente al tipo de orden. Cabe señalar que los errores de cierre se analizan mínimamente, por lo que usted debe añadir su propio algoritmo para procesar dicha situación.


Borrado de órdenes pendientes

La operación de borrado de órdenes pendientes es muy parecida a la de cierre de órdenes de mercado. La única diferencia es que no contiene un array de volúmenes, ya que para borrar una orden pendiente sólo necesitamos saber su ticket.

/**
      5. 
        a) Borrar una orden pendiente por hora
        b) Borrar una orden pendiente por condición
*/
   // tickets de las órdenes a borrar
   int ticketsToDelete[];
 
   // preparar un array de tickets con las órdenes a borrar
   if (trSignal!=OP_BALANCE) 
        PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // borrar las órdenes especificadas
   DeletePendingOrders(ticketsToDelete);

Por consiguiente, la sintaxis de este bloque de funciones es muy similar, por lo que en esta ocasión adjunto el código sin extenderme demasiado en las explicaciones:


//+---------------------------------------------------------------------+
//|  Preparar un array de tickets para borrar órdenes pendientes        |
//+---------------------------------------------------------------------+
void PrepareTicketsToDelete(int signal, bool Revers, int & ticketsDelete[],double arrayTickets[][9])
   {
   int size=ArrayRange(arrayTickets,0);
//----
   if (size==0) return;
   ArrayResize(ticketsDelete,0);
 
   int i,type,ticket,deleteSize;
   for (i=0;i<size;i++)
      {
      type=arrayTickets[i][1];
      // si no es una orden pendiente, saltar
      if (type<=OP_SELL) continue;
      
      if (Revers) // revertir el tipo de orden
         {
         switch (type)
            {
            case OP_BUYLIMIT : type = OP_SELLSTOP; break;
            case OP_SELLLIMIT: type = OP_BUYSTOP  ; break;
            case OP_BUYSTOP  : type = OP_SELLLIMIT; break;
            case OP_SELLSTOP : type = OP_BUYLIMIT ; break;
            }
         }
 
      // aquí decidimos el destino de las órdenes pendientes
      //  si la dejamos o la añadimos al array de órdenes a cerrar
      //  aquí mostraremos un ejemplo de retención de señal de compra 
      // órdenes pendientes OP_BUYLIMIT y OP_BUYSTOP
      // lo mismo para las señales de venta
      if (type==OP_BUYLIMIT || OP_BUYSTOP)
         {
         //  
         // código que nos permite conservar la orden de compra
         // por ejemplo
         if (signal==OP_BUY) continue;
         }
      
      if (type==OP_SELLLIMIT || OP_SELLSTOP)
         {
         //  
         // código que nos permite conservar la orden de venta
         // por ejemplo
         if (signal==OP_SELL) continue;
         }
 
      deleteSize=ArraySize(ticketsDelete);
      ArrayResize(ticketsDelete,deleteSize+1);
      ticketsDelete[deleteSize] = arrayTickets[i][0];
      }
//----
   return;   
   }



Apertura de una posición a mercado y colocación de órdenes pendientes

Solo nos quedan dos operaciones. Hemos aprendido a recibir señales de trading, preparar la lista de órdenes, modificar, cerrar y suprimir órdenes. En esta sección aprenderemos a abrir posiciones a mercado cuando sea conveniente, y a colocar órdenes pendientes.

/**
      6. 
        a) Abrir una orden a mercado
        b) Colocar una orden pendiente sin fecha de expiración
        c) Colocar una orden pendiente con fecha de expiración
*/
 
   if (trSignal!=OP_BALANCE) 
           OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);

   if (trSignal!=OP_BALANCE) 
           SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, pendingTP,pendingComment);

La primera función, OpenMarketOrder(), obtiene los datos de entrada necesarios incluyendo el signo del sistema inverso.

No hay nada complicado en esta función a excepción del procesamiento de la situación inversa. En el sistema inverso hay que reposar SL y TP y añadir un desplazamiento igual al valor del spread. Es importante subrayar que tanto el Stop Loss como el Take Profit pueden ser igual a cero.

///+------------------------------------------------------------------+
//|  Abre una posición a mercado                                      |
//+-------------------------------------------------------------------+
void OpenMarketOrder(bool   reversTrade,// signo del sistema inverso
                     int    Type,       // tipo de orden - OP_BUY o OP_SELL
                     double lots,       // volumen de la posición a abrir
                     double SL,         // nivel de StopLoss
                     double TP,         // nivel de TakeProfit
                     string comment)    // comentario sobre la orden
   {
 
   //Print("Abrir orden Type=",Type,"  lots=",lots,"  SL=",SL,"TP=",TP,
        " comment=",comment,"  ExpertMagicNumber=",ExpertMagicNumber);
   int openType;
   
   if (reversTrade)                       // invertir las señales
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
      if (Type==OP_BUY)  
         {
         openType = OP_SELL; // la compra se convierte en venta
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELL) 
         {
         openType = OP_BUY;  // la venta se convierte en compra
         temp = SL;
         if (TP!=0) SL = NormalizeDouble(TP-Point*spread,digits);
            else SL=0;
         if (temp!=0) TP = NormalizeDouble(temp-Point*spread,digits);
            else TP=0;
         }
      }
   else
      {
      openType=Type;
      }   
//----
 
   if (lots==0) return;
   
   RefreshRates();
   double price;
   color Color;
   if (openType==OP_BUY) 
      {
      price = Ask;
      Color = Blue; 
      }
   if (openType==OP_SELL) 
      {
      price=Bid;
      Color = Red; 
      }
   bool ticket = OrderSend(Symbol(),openType,lots,price,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (ticket>0)
      {
      Print("Error en la apertura de orden a mercado");
      Print("Type=",openType,"  lots=",lots,"  SL=",SL,"TP=",TP," comment=",comment,
        "  ExpertMagicNumber=",ExpertMagicNumber);
      //  procesamiento de la situación, código independiente
      }
//----
   return;
   }

La función SendPendingOrder() es un poco más complicada porque maneja dos tipos de órdenes pendientes de compra, y otros dos de venta. Por todo lo demás es como OpenMarketOrder().

//+---------------------------------------------------------------------+
//|  Coloca una orden pendiente                                         |
//+---------------------------------------------------------------------+
void SendPendingOrder(bool   reversTrade,// signo del sistema inverso
                      int    Type,
                      double OpenPrice, 
                      double Lots, 
                      double SL, 
                      double TP,
                      string comment)
   {
   //Print("SendPendingOrder()  Type=",Type);
   
   if (Type==-1) return; 
//----
 
   int openType;
   
   if (reversTrade)    // tipo de orden inversa y niveles
      {
      double temp;
      int spread = MarketInfo(Symbol(),MODE_SPREAD);
      int digits = MarketInfo(Symbol(),MODE_DIGITS);
 
      if (Type==OP_BUYLIMIT || Type==OP_BUYSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice - spread*Point,digits);
         temp=SL;
         if (TP!=0)  SL = NormalizeDouble(TP+Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp+Point*spread,digits);
            else TP=0;
         }
      if (Type==OP_SELLLIMIT || Type==OP_SELLSTOP)
         {
         OpenPrice = NormalizeDouble(OpenPrice + spread*Point,digits);
         temp=SL;
         if (TP!=0) SL = NormalizeDouble(TP - Point*spread,digits);
            else SL=0;
         if (temp!=0)TP = NormalizeDouble(temp - Point*spread,digits);
            else TP=0;
         }
 
      switch (Type)
         {
         case OP_BUYLIMIT:  openType = OP_SELLSTOP ; break;
         case OP_SELLLIMIT: openType = OP_BUYSTOP  ; break;
         case OP_BUYSTOP:   openType = OP_SELLLIMIT; break;
         case OP_SELLSTOP:  openType = OP_BUYLIMIT ; break;
         default: Print("Tipo de orden no válido Type=",Type," en la función SendPendingOrder()!!!");                           
         
         }
      }
   else openType = Type;   
      
   color Color;
   if (openType==OP_SELLLIMIT || openType==OP_SELLSTOP)  Color = Red;
   if (openType==OP_BUYLIMIT  || openType==OP_BUYSTOP)   Color = Blue;
 
   bool res = OrderSend(Symbol(),openType,Lots,OpenPrice,Slippage,SL,TP,comment,ExpertMagicNumber,0,Color); 
   if (!res)
      {
      Print("Fallo al colocar orden pendiente");
      //  procesamiento de la situación, código independiente
      }
 
//----
   return;
   }

En ambas funciones el procesamiento del error de la solicitud de la orden es mínimo; usted puede insertar su propio código para gestionarlo.


Versión final de la función start()

Una vez creadas las funciones, podemos echar un vistazo a la función start(), que ha pasado de la plantilla de texto al siguiente código completo:

//+---------------------------------------------------------------------+
//| función de inicio del experto                                       |
//+---------------------------------------------------------------------+
int start()
  {
   // establecemos a cero el tamaño del array
   ArrayResize(Tickets,0);
   //ArrayResize(CommentsTicket,0);
   
   // obtener los arrays de órdenes "amigas"
   PrepareTickets(Tickets,CommentsTicket,ExpertMagicNumber);
   
//----
 
/**
      1. Señales de trading. Recepción de señales de trading
      a) Todos los ticks                                 (TradeSignalEveryTick=true)
      b) Todas las barras en el periodo predeterminado   (TradeSignalBarPeriod=...)
      OP_BUY      - compra
      OP_SELL     - venta
      OP_BALANCE  - sin señal
*/
 
 
   int trSignal=getTradeSignal(TradeDay,ReversDay,
                       TradeSignalEveryTick,TradeSignalBarPeriod);
                       
/*   
   if (trSignal==OP_BUY) Print("Señal de compra");                    
   if (trSignal==OP_SELL) Print("Señal de venta");                    
   if (trSignal!=OP_SELL && trSignal!=OP_BUY) Print("La señal actual es igual a ",trSignal);
*/
 
/**
      2. 
        a) Calcular SL y TP en toda orden abierta
        b) Calcular OpenPrice, SL, TP y Lots en la nueva orden
        c) Calcular OpenPrice, SL y TP en toda orden pendiente
*/
 
   CalculateSL_and_TP(ReversTradeSignal,Tickets,newSL_and_TP);
 
 
   double marketLots,marketSL,marketTP;
   int marketType, pendingType;
   string marketComment, pendingComment;
   double pendingOpenPrice, pendingLots, pendingSL, pendingTP;
 
   CalculateNewMarketValues(trSignal, marketType, marketLots,marketSL,marketTP,marketComment);
   CalculateNewPendingValues(trSignal, pendingType, pendingOpenPrice, pendingLots, pendingSL,
        &nbsp;pendingTP, pendingComment);
 
/**
      3. 
        a) Modificación de la orden abierta en cada tick (SL y TP)        
               (ModifyMarketOrderEveryTick = true)
               
        b) Modificación de la orden pendiente en cada tick (OpenPrice, SL y TP)
               (ModifyPendingEveryTick = true)
        
        c) Modificación de la orden abierta en la nueva barra del período predeterminado (SL y TP)
               (ModifyMarketBarPeriod = ...)
        
        d) Modificación de la orden pendiente en la nueva barra del período predeterminado (OpenPrice, SL y TP)
               (ModifyPendingBarPeriod = ...)
        
*/
 
   ModifyMarkets(ReversTradeSignal,ModifyMarketOrderEveryTick,ModifyMarketBarPeriod,newSL_and_TP);
   ModifyPendings(ReversTradeSignal,ModifyPendingEveryTick,ModifyPendingBarPeriod,newSL_and_TP);
 
/**
      4. 
        a) Cerrar una orden abierta por hora
        b) Cerrar una orden abierta por señal
*/
   // tickets de las órdenes a cerrar
   int ticketsToClose[][2];
   double lotsToClose[]; 
   
   // establecemos a cero los tamaños de los arrays
   ArrayResize(ticketsToClose,0);
   ArrayResize(lotsToClose,0);
   
   // preparar un array de ticket para el cierre
   if (trSignal!=OP_BALANCE) PrepareTicketsToClose(trSignal,ReversTradeSignal,ticketsToClose,lotsToClose,Tickets);
   
   // cerrar las órdenes especificadas
   CloseMarketOrders(ticketsToClose,lotsToClose);
 
/**
      5. 
        a) Borrar una orden pendiente por hora
        b) Borrar una orden pendiente por condición
*/
   // tickets de las órdenes a borrar
   int ticketsToDelete[];
 
   // preparar un array de ticket para las órdenes a borrar
   if (trSignal!=OP_BALANCE) PrepareTicketsToDelete(trSignal,ReversTradeSignal,ticketsToDelete,Tickets);
 
   // borrar las órdenes especificadas
   DeletePendingOrders(ticketsToDelete);
 
/**
      6. 
        a) Abrir una orden a mercado
        b) Colocar una orden pendiente sin expiración
        c) Colocar una orden pendiente con fecha de expiración
*/
 
   if (trSignal!=OP_BALANCE) 
      OpenMarketOrder(ReversTradeSignal,marketType,marketLots,marketSL,marketTP,marketComment);
   
   if (trSignal!=OP_BALANCE) 
      SendPendingOrder(ReversTradeSignal,pendingType,pendingOpenPrice, pendingLots, pendingSL, 
            pendingTP,pendingComment);
//----
   return(0);
  }
//+---------------------------------------------------------------------+

La lógica de las operaciones del Asesor Experto es completamente visible, no obstante, los detalles de la implementación se encapsulan en las funciones correspondientes. Durante la depuración del programa buscaremos los errores que hayamos podido cometer. Además, la lógica del Asesor Experto está claramente separada. Si en algún momento queremos cambiar el algoritmo de búsqueda de órdenes "amigas" sabemos a qué función tenemos que acudir; de igual modo, sabemos dónde insertar las condiciones de apertura o cierre de posiciones, y en qué función tenemos que modificar el Trailing Stop cuando haga falta. Lo único que nos queda por ver es un ejemplo práctico de uso de plantilla.


Ejemplo de uso

En este artículo se adjunta la plantilla Template_EA.mqt. Puede guardarla como Expert.mqt en la carpeta C:\Program Files\MetaTrader 4\experts\templates\. Cuando cree un nuevo EA, si utiliza este archivo como plantilla tendrá a su disposición todas las funciones anteriores de forma automática. Hay otra alternativa: guardar el archivo en la misma carpeta sin cambiar el nombre. En este caso podrá especificar manualmente este archivo como plantilla en el momento de crear un nuevo Asesor Experto.



Seleccionaremos nuestra plantilla, Template_EA, y clicaremos en "Siguiente". Aplicaremos la siguiente estrategia sencilla:

  • se forma una señal de compra cuando la línea de señal del estocástico sale de la zona de sobreventa y rompe a través del nivel predeterminado DownLevel, de abajo hacia arriba (de forma predeterminada es 10)
  • se forma una señal de venta cuando la línea de señal del estocástico sale de la zona de sobrecompra y rompe a través del nivel predeterminado UpLevel, de arriba hacia abajo (de forma predeterminada es 90)
  • se pone un StopLoss protector a una distancia de 100 puntos del precio de apertura (se puede cambiar)
  • se pone un Take Profit a una distancia de 100 puntos del precio de apertura (se puede cambiar)
  • los parámetros del Estocástico se establecen con los parámetros externos del EA, que también se pueden cambiar.

Llamemos a nuestro EA Osc_test e introduzcamos los parámetros externos necesarios:



Al clicar en "Finalizar" el Asistente de Asesores Expertos insertará estos parámetros en el EA que hemos creado con la plantilla de arriba.

Añadimos pues los parámetros necesarios para la creación de un EA:

//+---------------------------------------------------------------------+
//|                                                        Osc_test.mq4 |
//|                         Copyright © 2007, MetaQuotes Software Corp. |
//|                                           https://www.metaquotes.net |
//+---------------------------------------------------------------------+
#property copyright "Copyright © 2007, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net"
 
//---- parámetros de entrada
extern int       Kperiod=18;//5;
extern int       Dperiod=14;//3;
extern int       slowPeriod=10;//3;
extern int       UpLevel=90;
extern int       DownLevel=10;
extern int       StopLoss=100;
extern int       TakeProfit=100;
extern double    Lots=0.1;
 
// constante "fuera del mercado"
#define OP_BALANCE 6

Ahora insertaremos en yourFunction() el código que genera señales de compraventa:

//+---------------------------------------------------------------------+
//|  Función que genera señales de trading                              |
//+---------------------------------------------------------------------+
int yourFunction(int workPeriod)
   {
   bool res=OP_BALANCE;
//----
   double prevValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,2);
   double currValue = iStochastic(Symbol(),workPeriod,Kperiod,Dperiod,slowPeriod,MODE_SMA,0,MODE_SIGNAL,1);
 
   if (currValue>DownLevel && prevValue<DownLevel) res=OP_BUY;
   if (currValue<UpLevel && prevValue>UpLevel) res=OP_SELL;
 
//----
   return (res);   
   }

Solo necesitamos cuatro líneas de código. Por último, hay que definir la función CalculateNewMarketValue() que prepara los datos para abrir una posición a mercado.

//+---------------------------------------------------------------------+
//|  Calcula los datos para abrir una nueva orden                       |
//+---------------------------------------------------------------------+
void CalculateNewMarketValues(int    trSignal,
                              int    & marketType,
                              double & marketLots, 
                              double & marketSL,
                              double & marketTP,
                              string & marketcomment
                              )
   {
   // si no hay señal de trading, salir
   //Print("CalculateNewMarketValues()  trSignal=",trSignal);
   if (trSignal==OP_BALANCE) return;
 
   marketType    =-1; // esto significa que no se abrirá ninguna orden
   marketLots    = 0;
   marketSL      = 0;
   marketTP      = 0;
   marketcomment = "";
 
 
//----
   //  inserte aquí su propio código para calcular todos los parámetros
 
   if (trSignal==OP_BUY  && StopLoss!=0) marketSL = Bid - StopLoss*Point;
   if (trSignal==OP_SELL && StopLoss!=0) marketSL = Ask + StopLoss*Point;
 
   if (trSignal==OP_BUY  && TakeProfit!=0) marketTP = Bid + TakeProfit*Point;
   if (trSignal==OP_SELL && TakeProfit!=0) marketTP = Ask - TakeProfit*Point;
 
   marketLots = Lots;
 
   if (trSignal==OP_BUY) marketType = OP_BUY;
   if (trSignal==OP_SELL) marketType = OP_SELL;
 
   marketcomment="test";
//----
   return;   
   }

Como se observa, ¡conseguimos lo anterior añadiendo solo cinco líneas de código! Hemos descrito así nuestra estrategia sencilla. Esta es una de las ventajas de esta aproximación.

Crear una plantilla requiere elaborar todos los detalles de una implementación EA típica. Sin embargo este esfuerzo se amortiza en el futuro al crear nuevas estrategias.


Ventajas adicionales

Hay algo más por venir. Probemos nuestro Asesor Experto en un símbolo y en un marco temporal determinados. Por ejemplo, EURUSD H1 con los parámetros predeterminados:


En este caso no importa si hay ganancias o no. Echemos un vistazo al resultado:


El número total de operaciones es 430, 241 son ventas y 189 son compras. Invirtamos el sistema: donde había operaciones de venta, compremos; y donde hay operaciones de compra, vendamos. Para ello estableceremos a 'true' el parámetro ReversTradeSignal. Esta es la indicación del sistema inverso:


Comenzamos las pruebas sin cambiar ningún parámetro. Y obtenemos estos resultados:


Efectivamente, ahora tenemos 241 operaciones de compra y 189 ventas. Las cantidades de compras y ventas se han invertido. El porcentaje de operaciones ganadoras también se invierte. ¡No ha hecho falta reescribir el Asesor Experto para comprobar cómo funciona el EA inverso!

Pero no solo eso. ¿Recuerda el parámetro TradeDay? De forma predeterminada vale cero, ¿pero qué sucede si nuestro EA opera solo los viernes? Entonces establecemos su valor a 5:


Realizamos operaciones sin modificar ningún parámetro. Este es el resultado. El resultado de las pruebas revela el comportamiento del EA, operando solo los viernes con el sistema inverso:


Solo quedan 81 operaciones de las 430 iniciales. Lo que significa que otras órdenes se llevaron a cabo en otros días de la semana. En este caso no es importante que el resultado sea rentable. Veamos el comportamiento del EA en el historial si hubiéramos operado todos los días excepto los viernes. Tan solo tenemos que cambiar el parámetro ReversDay a 'true'.


Este es el resultado obtenido en las pruebas:


Hay 430 operaciones, quitando las del viernes (81) obtenemos 349. Los números se suman: 430-81=349. Adí que hemos invertido correctamente los días operativos de trading. Sin necesidad de reprogramar el EA.


Conclusión

Reconozco que este artículo se puede mejorar en algunos aspectos. Por una parte es demasiado conciso y no ofrece descripciones detalladas de algunas funciones. Y por otro lado es demasiado largo para una primera lectura. Espero en todo caso que sirva para mostrar mi apoyo a este enfoque de creación de EAs en MQL4. Esta aproximación es la más adecuada para trabajar en equipo; en efecto, es más eficiente aplicar una tormenta de ideas en la creación de plantillas que invertir esfuerzo en un solo algoritmo de trading.

Aproveche al máximo todas las opciones que tiene a su disposición. Cree sus propias plantillas para hacer trading de forma simultánea en varios símbolos, y añada lógica de tratamiento de errores para procesar los valores devueltos por las funciones de trading. Añada funciones informativas, funciones de logging, y cree informes especiales de los resultados de las pruebas, o en tiempo real. Recuerde que puede limitar algunas características de las sesiones de trading, no solo por días de la semana, sino también por horas. También puede encapsular todas las funciones de servicio (interfaz) en un archivo *.mqh aparte, con el objetivo de localizar las funciones que se tienen que redefinir. Las plantillas ofrecen un amplio abanico de posibilidades.

La ventaja principal es que solo se tienen que desarrollar una vez, y luego se pueden utilizar de manera continuada. Al mismo tiempo que disminuyen la probabilidad de error de los EAs.

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

Archivos adjuntos |
Osc_test.mq4 (36.96 KB)
Template_EA.mqt (35.82 KB)
Cómo proteger su Asesor Experto cuando tradea en la Bolsa de Moscú Cómo proteger su Asesor Experto cuando tradea en la Bolsa de Moscú
En este artículo se describen detalladamente los métodos de trabajo que sirven para garantizar la seguridad de la ejecución de operaciones comerciales en los mercados bursátiles y en los mercados de baja liquidez tomando de ejemplo la Sección de Derivados de la Bolsa de Moscú. Este artículo es la continuación lógica del artículo “Principios de formación de precios en el mercado bursátil tomando de ejemplo la Sección de Derivados de la Bolsa de Moscú” que contiene los principios teóricos del trading bursátil, pero lleva el carácter más práctico.
Asesores Expertos basados en sistemas populares de trading, y un poco de alquimia en la optimización de robots Asesores Expertos basados en sistemas populares de trading, y un poco de alquimia en la optimización de robots
Este artículo trata sobre la implementación de algoritmos de sistemas de trading sencillos. De modo que será de gran utilidad para los traders principiantes, así como para aquellas personas que se inician en la programación de EAs.
Asesores Expertos basados en sistemas populares de trading, y un poco de alquimia en la optimización de robots (Parte II) Asesores Expertos basados en sistemas populares de trading, y un poco de alquimia en la optimización de robots (Parte II)
En este artículo el autor continúa analizando la implementación de algoritmos de los sistemas de trading más sencillos, y describe algunos detalles relevantes sobre la optimización de resultados. Los traders principiantes y los desarrolladores noveles de EA encontrarán especialmente útil este texto.
A la caza de la tendencia A la caza de la tendencia
El presente artículo describe un algoritmo para aumentar el volumen de una operación ganadora. Se adjunta la implementación correspondiente en el lenguaje MQL4.