English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
El prototipo del Robot de trading

El prototipo del Robot de trading

MetaTrader 5Ejemplos | 18 diciembre 2013, 08:15
2 170 0
---
---

Introducción

El ciclo de vida de cualquier sistema de trading se reduce a la apertura y cierre de posiciones. Sin lugar a duda. Pero cuando se trata de la implementación del algoritmo, pues hay tantas opiniones como programadores. Todo el mundo será capaz de resolver el mismo problema a su manera, pero con el mismo resultado final.

A lo largo de los años, se han intentado varios enfoques para la realización de una lógica y estructura del experto a través de la programación. Por el momento, se puede afirmar que se ha establecido una plantilla con una pauta clara que se usa en todos los códigos.

Este enfoque no es universal al 100%, pero puede cambiar tu manera de diseñar la lógica del experto. La cuestión no trata de las oportunidades de trabajar con las ordenes que quieres utilizar con el experto. Todo el sunto está en el principio de creación de un modelo de trading.


1. Principios de diseño de los sistemas de trading y tipos de fuentes de eventos

El enfoque básico para el diseño de un algoritmo, utilizado por la mayoría, es hacer el seguimiento de una posición desde su apertura hasta su cierre. Este es un enfoque lineal. Y si quieres hacer cambios en el código, que a menudo lleva a grandes complicaciones, ya que surge un gran número de condiciones y el código adquiere nuevas ramas de análisis.

La mejor solución para modelar un robot de trading es "servir las condiciones". Y un principio fundamental de este análisis no es cómo surgen estas condiciones del experto y sus posiciones y órdenes, sino lo que debemos hacer con ellas ahora. Este principio básico está cambiando radicalmente la gestión del trading y simplifica el desarrollo de los códigos.

Vamos a abordarlo con más profundidad.

1.1. El principio de "Servir las condiciones"

Como se ha mencionado antes, el experto no necesita saber cómo se ha llevado a cabo el estado actual. Debe saber qué hacer con el ahora según su entorno (valores del parámetro, propiedades de órdenes guardadas, etc.).

Este principio está directamente relacionado con el hecho de que el experto existe sólo de un bucle a otro (en particular de tick a tick), y no le debe importar lo que ocurre con las órdenes anteriores al tick. Por lo tanto, debes utilizar un enfoque orientado a eventos de gestión de órdenes. Es decir, en el tick actual el experto guarda su estado, que será el punto de inicio para la decisión sobre el siguiente tick.

Por ejemplo, debes eliminar todas las órdenes pendientes del experto y sólo entonces puedes continuar analizando los indicadores y colocando nuevas órdenes. La mayoría de los ejemplos de códigos que hemos visto utilizan el bucle "while (true) {intenta eliminar}" o un bucle más suave "while (k < 1000) {intenta eliminar}". Omitiremos la variante de llamada única del comando eliminar sin análisis de errores.

Este método es lineal, se "cuelga" el experto por un tiempo indefinido.

Por lo tanto, sería más correcto no meter el experto en bucles, salvo para almacenar órdenes o eliminar órdenes, de este modo, a cada nuevo tick se comprobará la orden mientras se intenta borrar la orden pendiente. En este caso, mientras el experto lee los parámetros del estado, sabe que en este momento tiene que borrar órdenes. Y tratará de eliminarlas. Si ocurre un error de trading, el experto simplemente bloqueará su análisis y funcionamiento hasta el próximo bucle.

1.2. El segundo principio más importante del diseño, es la máxima abstracción posible desde la dirección de la posición en cuestión (Compra/Venta), la divisa y el gráfico. Todas las funciones del experto se deben implementar de este modo, que está dirección o símbolo sean analizados en casos excepcionales en los que realmente no se puede evitar (por ejemplo, si tenemos en cuenta el crecimiento favorable del precio para la posición abierta, si bien existen distintos opciones para evitar detalles específicos). Intenta siempre evitar dicho diseño de "bajo nivel". Esto reducirá por lo menos a la mitad el código y el procesamiento de escritura de funciones. Y esto los haría "independientes del trading". 

La implementación de este principio es reemplazar el análisis explícito de los tipos de órdenes, propiedades de símbolos y parámetros calculados dependientes por funciones macro. Más adelante en el artículo veremos su implementación en detalle.

1.3. Tercer principio segmentación del algoritmo en unidades lógicas (módulos independientes)

En la práctica, podemos decir que el mejor enfoque es la separación las operaciones del experto en funciones individuales. Creo que estarás de acuerdo en que es difícil escribir todo el algoritmo del experto en una función, y que esto complica el análisis y la edición posterior. Así que no tenemos que hacerlo en MQL5, ya que ahora proporciona un control casi completo de tu entorno.

Por lo tanto, las unidades lógicas (es decir, apertura, seguimiento, cierre de ordenes) se deben implementar por separado la una de la otra con un análisis completo de los parámetros del entorno y de los eventos. Con este enfoque, el diseño del experto se hace flexible. Puedes añadir fácilmente nuevos módulos independientes en el sin tocar los que ya existen, o deshabilitar los módulos existentes sin modificar el código principal.

Estos tres principios hacen posible la creación de un prototipo único para todos los expertos, que puedes modificar y adaptar fácilmente a cualquier tarea.

Las fuentes de eventos para los sistemas expertos son:


1. Indicadores. Un ejemplo es el análisis de las líneas de valores del indicador, sus intersecciones, combinaciones, etc. Los indicadores también pueden ser: el tiempo actual, los datos obtenidos de Internet, etc. En la mayoría de los casos, se utilizan los eventos del indicador para señalar la apertura y cierre de las órdenes. Salvo para ajustarlos (en general Trailing Stop Loss o orden pendiente para el indicador).  

Por ejemplo, a la implementación práctica de un indicador se le puede llamar un experto, que analiza la intersección del indicador MA rápido y lento con mayor apertura en el sentido de la intersección. 

2. Ordenes existentes, posiciones y su estado. Por ejemplo, la pérdida actual o el volumen del beneficio, la presencia/ausencia de posiciones u órdenes pendientes, el beneficio de la posición cerrada, etc. La implementación práctica de estos eventos es mucho más amplia y más diversa, ya que hay más opciones que las relacionan que con los eventos del indicador.

El ejemplo más sencillo de un experto, basado únicamente en el evento de trading, se rellena para promediar una posición existente y colocar su salida en el beneficio deseado. Es decir, la presencia de pérdidas en una posición disponible sería un evento para colocar una nueva orden de promediado.

O, por ejemplo, Trailing Stop Loss. Esta función comprueba un evento, cuando el precio se mueve en beneficios para un determinado puntos desde el Stop Loss anterior. Como resultado, el experto mueve el Stop Loss detrás del precio.

3. Eventos externos. Aunque esto no suele ocurrir en un sistema puramente experto, pero en general hay que tenerlo en cuenta para tomar decisiones. Esto incluye ajustar las órdenes, posiciones, procesar los errores de trading, procesar los eventos del gráfico (moviendo/creando/borrando objetos, pulsando botones, etc.). En general, estos son eventos, que no se pueden comprobar en el historial y ocurren sólo cuando el experto está ejecutándose.

Un ejemplo destacado de este tipo de expertos son los sistemas de información de trading con un control gráfico del trading.

Todos los expertos se basan en la combinación de estas tres fuentes de eventos

2. La clase base CExpertAdvisor -constructor experto

¿Cuál es la función de un experto de trading? En el siguiente diagrama se muestra el esquema general de interacciones del programa MQL.

Figura 1. Esquema general de interacciones de los elementos del programa MQL.

Figura 1. Esquema general de interacciones de los elementos del programa MQL.

Como puedes observar en el esquema, primero está la entrada al bucle de trabajo (esta puede ser un tick o una señal del temporizador). En esta etapa se puede filtrar el tick sin procesarlo en el primer bloque. Esto ocurre cuando no hace falta el experto para funcionar en cada tick, pero sólo en una barra nueva, o cuando el experto está simplemente deshabilitado.

A continuación, el programa entra en el segundo bloque -módulos para trabajar con órdenes y posiciones, y sólo entonces se llama a los bloques de procesamiento de eventos desde los módulos. Cada módulo puede consultar el evento que le interesa sólo.  

Se puede llamar a esta secuencia como en el esquema con lógica directa, ya que primero determina QUÉ hará el experto (qué eventos utiliza el módulo de procesamiento), y sólo entonces CÓMO y PORQUÉ lo hará (obtener las señales del evento).

La lógica directa es coherente con nuestra percepción del mundo y de la lógica universal. Después de todo, el ser humano piensa primero en conceptos concretos, los resume, los clasifica e identifica la relación entre los mismos.

En este sentido, el diseño de expertos no es la excepción. En primer lugar, se declara lo que debe hacer un experto (abrir y cerrar posiciones, parada de protección), y sólo entonces se indica en que eventos y cómo debe hacerlo. Pero en cualquier caso, esto no vale a la inversa: se recibe la señal y luego se piensa dónde y cómo procesarla. Esta es la lógica inversa, y es mejor no utilizarla, ya que el resultado obtenido sería un código engorroso con un gran número de ramas de condiciones.

He aquí un ejemplo de lógica directa e inversa. Haz la apertura/cierre mediante la señal RSI.

  • En la lógica inversa el experto comienza recibiendo el valor del indicador, y después comprueba la dirección de la señal y qué es lo que tienes que hacer con la posición: abrir la Compra o cerrar la Venta, o al revés, abrir la Venta y cerrar la compra. Es decir, que el punto de entrada sirve para recibir y analizar la señal.
  • En la lógica directa es justo lo contrario. El experto tiene dos módulos para abrir y cerrar posiciones, y simplemente comprueba las condiciones para la ejecución de estos módulos. Es decir, después de entrar en el módulo de apertura, el experto recibe el valor del indicador y comprueba si es una señal para abrir una posición. A continuación, después de entrar al módulo de cierre de órdenes, el experto comprueba si se trata de una señal para cerrar una posición. Es decir, no ha ningún punto de entrada, hay módulos que funcionan independientemente del estado de análisis del sistema (el primer principio del diseño).

Ahora, si quieres complicar el experto, será mucho más fácil hacerlo con la segunda opción que con la primera. Bastaría con crear un nuevo módulo de procesamiento de eventos.

En la primera opción hubieras tenido que repasar la estructura del procesamiento de la señal o incrustarla como una función separada.

Recomendación: Al describir el sistema de trading, no empieces con expresiones como "1. Obtener la señal ... abrir orden", sino dividir inmediatamente en secciones: "a) La condición para abrir órdenes, b) Condiciones para mantener las órdenes, etc." y analizar las señales necesarias en cada una.

Para entender mejor este enfoque, se presentan aquí los distintos esquemas de trabajo en el marco de cuatro expertos diferentes.

Figura 2. Ejemplos de implementación de expertos

Figura 2. Ejemplos de implementación de expertos

a). Un experto basado sólo en las señales de algún indicador. Puede abrir y cerrar posiciones cuando cambia la señal. Ejemplo -un experto MA.
b). Un experto con control gráfico del trading.
c). Un experto basado en indicadores, pero dispone además de Trailing Stop Loss y tiempo de funcionamiento. Ejemplo -revender a muy corto plazo con operaciones abiertas mediante el indicador MA.
d). Un experto sin indicadores, con promediado de posiciones. Comprueba los parámetros de la posición sólo cuando se abre una nueva barra. Ejemplo -Experto de promediado.

Como se puede observar en los esquemas, se puede describir fácilmente cualquier sistema de trading mediante la lógica directa.


3. Implementación de la clase Experto

Crear una clase mediante las reglas y requisitos mencionados arriba, y que será base para todos los expertos futuros.

Estas van a ser las funcionalidades mínimas que debe incluir la clase CExpertAdvisor:

1. Inicialización

  • Registrar los indicadores
  • Establecer los valores iniciales de los parámetros
  • Ajustar al símbolo requerido y el período de tiempo

2. Funciones para la obtención de las señales

  • Tiempo de funcionamiento permitido (intervalos de trading)
  • Definir la señal para abrir/cerrar posiciones u órdenes
  • Definir el filtro (tendencia, tiempo, etc.)  
  • Temporizador de Iniciar/Detener

3. Funciones de servicio

  • Calcular el precio de apertura, los niveles de Stop Loss y Take Profit, el volumen de la orden
  • Enviar peticiones de trading (abrir, cerrar, modificar)

4. Módulos de trading

  • Procesar las señales y los filtros
  • Controlar las posiciones y las órdenes
  • Trabajar en las funciones del experto: OnTrade(), OnTimer(), OnTester(), OnChartEvent().

5. Desinicialización

  • Mensajes de salida, informes
  • Borrar el gráfico, descargar los indicadores

Se dividen todas las funciones de la clase en tres grupos. A continuación, se presenta el esquema general de las funciones anidadas y sus descripciones.

Figura 3. Esquema de las funciones anidadas de un experto

Figura 3. Esquema de las funciones anidadas de un experto

1. Funciones macro 

Este pequeño grupo de funciones es la base para trabajar con los tipos de órdenes, los parámetros de símbolos y los valores de precios para colocar órdenes (apertura y cierre). Estas funciones macro proporcionan el segundo principio del diseño -la abstracción. Trabajan en el contexto del símbolo utilizado por el experto.

Las funciones macro de conversión de tipos trabajan en función de la evolución del mercado -compra o venta. Por lo tanto, para no crear tus propias constantes, es mejor que utilices las que ya existen - ORDER_TYPE_BUY y ORDER_TYPE_SELL. Estos son algunos ejemplos del uso de macros y sus resultados.

   //--- Macro de conversión de tipo
   long       BaseType(long dir);        // devuelve el tipo base de la orden para la dirección indicada
   long       ReversType(long dir);      // devuelve el tipo inverso de la orden para la dirección indicada
   long       StopType(long dir);        // devuelve el tipo detener la orden para la dirección indicada
   long       LimitType(long dir);       // devuelve el tipo límite de la orden para la dirección indicada

   //--- Macro de normalización
   double     BasePrice(long dir);       // devuelve el precio Bid/Ask para la dirección indicada
   double     ReversPrice(long dir);     // devuelve el precio Bid/Ask para la dirección inversa

   long dir,newdir;
   dir=ORDER_TYPE_BUY;
   newdir=ReversType(dir);               // newdir=ORDER_TYPE_SELL
   newdir=StopType(dir);                 // newdir=ORDER_TYPE_BUY_STOP
   newdir=LimitType(dir);                // newdir=ORDER_TYPE_BUY_LIMIT
   newdir=BaseType(newdir);              // newdir=ORDER_TYPE_BUY

   double price;
   price=BasePrice(dir);                 // price=Ask
   price=ReversPrice(dir);               // price=Bid

En el desarrollo de los expertos la macro te permite no indicar la dirección procesada y te ayuda a crear un código más compacto.

2. Funciones de servicio

Se diseñan estas funciones para trabajar con órdenes y posiciones. Son de bajo nivel, igual que las funciones macro. Por comodidad, se pueden dividir en dos categorías: funciones de información y funciones de ejecución. Todas realizan un sólo tipo de acción, sin analizar ningún evento. Llevan a cabo órdenes desde controladores expertos de alto nivel.

Ejemplos de funciones de información: encontrar el precio máximo del precio de apertura de las órdenes pendientes actuales, averiguar cómo se ha cerrado una posición -con beneficio o pérdida; obtener el número y la lista de los tickets de las órdenes de los expertos, etc.

Ejemplos de funciones de ejecución: cerrar las órdenes indicadas; modificación del Stop Loss en la posición indicada, etc.

Este grupo es el más amplio. Este es el tipo de funcionalidad, en el que se basa todo el trabajo del experto. Se pueden encontrar numerosos ejemplos de estas funciones en el foro https://www.mql5.com/ru/forum/107476. Pero aparte de esto, la librería estándar de MQL5 ya contiene clases que asumen parte del trabajo de colocar órdenes y posiciones, sobre todo -la clase CTrade.

Pero cualquier tarea tuya requerirá la creación de nuevas implementaciones o ligeras modificaciones a las que ya existen.

3. Módulos de procesamiento de eventos

Este es un grupo de funciones con una superestructura de un nivel más alto que los dos primeros grupos. Como se ha mencionado antes, estos son bloques ya hechos, utilizados en la implementación de tu experto. En general, están incluidos en las funciones de procesamiento de eventos del programa MQL: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent(). Este grupo no es muy amplio, y se pueden ajustar los contenidos de estos módulos de una tarea a otra. Pero fundamentalmente, nada ha cambiado.  

En estos módulos, todo debe ser abstracto (el segundo principio del diseño) para que se pueda llamar al módulo tanto para comprar como para vender. Esto se consigue, por supuesto, mediante macros.

Procede entonces con la implementación

1. Inicialización, Desinicialización

class CExpertAdvisor
  {
protected:
   bool              m_bInit;       // flag de inicialización correcta
   ulong             m_magic;       // número mágico del experto
   string              m_smb;       // símbolo sobre el cual trabaja el experto
   ENUM_TIMEFRAMES      m_tf;       // período de tiempo de funcionamiento
   CSymbolInfo      m_smbinf;       // parámetros del símbolo
   int               m_timer;       // tiempo para el temporizador

public:
   double              m_pnt;       // considerar cotizaciones con 5/3 dígitos los stops
   CTrade            m_trade;       // objeto para la ejecución de las órdenes de trading
   string              m_inf;       // cadena de comentario para la información sobre el funcionamiento del experto

Este es el ajuste de parámetros mínimo para que puedan trabajar las funciones del experto.

Los parámetros m_smb y m_tf están puestos en las propiedades del experto especialmente para decirle al mismo con qué divisa y que período trabajar. Por ejemplo, si pones m_smb = "USDJPY", el experto trabajará con este símbolo, sin importar con qué símbolo se ejecutó. Si pones tf = PERIOD_H1, entonces todas las señales y análisis de indicadores tendrán lugar en el gráfico H1. 

Además, existen los métodos de clase. Los tres primeros métodos son la inicialización y desinicialización de un experto.

public:
   //---Inicialización
   void              CExpertAdvisor();                               // constructor
   void             ~CExpertAdvisor();                               // destructor
   virtual bool      Init(long magic,string smb,ENUM_TIMEFRAMES tf); // inicialización

El constructor y el destructor de la clase base no hacen nada.

El método Init() realiza la inicialización de los parámetros el experto mediante el símbolo, el período de tiempo y el número mágico.

//------------------------------------------------------------------ CExpertAdvisor
void CExpertAdvisor::CExpertAdvisor()
  {
   m_bInit=false;
  }
//------------------------------------------------------------------ ~CExpertAdvisor
void CExpertAdvisor::~CExpertAdvisor()
  {
  }
//------------------------------------------------------------------ Init
bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf)
  {
   m_magic=magic; m_smb=smb; m_tf=tf;         // definir los parámetros de inicialización
   m_smbinf.Name(m_smb);                      // inicializar el símbolo
   m_pnt=m_smbinf.Point();                    // calcular el multiplicador para cotización de 5/3 dígitos
   if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10;  
   m_trade.SetExpertMagicNumber(m_magic);     // definir el número mágico para el experto

   m_bInit=true; return(true);                // trading permitido
  }

2. Funciones para la obtención de las señales

Estas funciones analizan el mercado y los indicadores.

   bool              CheckNewBar();                          // comprobar si hay una barra nueva
   bool              CheckTime(datetime start,datetime end); // comprobar el tiempo de trading permitido
   virtual long      CheckSignal(bool bEntry);               // comprobar la señal
   virtual bool      CheckFilter(long dir);                  // comprobar el filtro de la dirección

Las dos primeras funciones tienen una implementación bastante específica y se pueden utilizar en otras clases heredadas de esta misma clase.

//------------------------------------------------------------------ CheckNewBar
bool CExpertAdvisor::CheckNewBar()          // función para comprobar si hay una barra nueva
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)      // copiar la barra
     { Print("CopyRates of ",m_smb," failed, no history"); return(false); }
   if(rt[1].tick_volume>1) return(false);   // check volume 
   return(true);
  }
//---------------------------------------------------------------   CheckTime
bool CExpertAdvisor::CheckTime(datetime start,datetime end)
  {
   datetime dt=TimeCurrent();                          // tiempo actual
   if(start<end) if(dt>=start && dt<end) return(true); // comprobar si estamos en el rango
   if(start>=end) if(dt>=start|| dt<end) return(true);
   return(false);
  }

Las dos últimas dependen siempre de esos indicadores que estás utilizando. Es sencillamente imposible configurar todas estas funciones para todos los casos.

Lo fundamental -es importante entender que las funciones de las señales CheckSignal() y CheckFilter() pueden analizar absolutamente todos los indicadores y sus combinaciones! Es decir, los módulos de trading en los cuales se incluyen estás señales posteriormente, son independientes.

Esto te permite utilizar el experto una vez escrito como plantilla para otros expertos que funcionan con el mismo principio. Sólo tienes que modificar los indicadores analizados y añadir nuevas condiciones de filtrado.

3. Las funciones de servicio

Como ya se ha mencionado, este grupo de funciones es el más amplio. Para los casos prácticos descritos en este artículo, bastaría con implementar estas cuatro funciones:

   double         CountLotByRisk(int dist,double risk,double lot); // calcular el lote en función del riesgo
   ulong          DealOpen(long dir,double lot,int SL,int TP);     // ejecutar la transacción con los parámetros indicados
   ulong          GetDealByOrder(ulong order);                     // obtener el ticket de transacción mediante el ticket de orden
   double         CountProfitByDeal(ulong ticket);                 // calcular el beneficio mediante el ticket de transacción
//------------------------------------------------------------------CountLotByRisk
double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calcular el lote en función del riesgo
  {
   if(dist==0 || risk==0) return(lot);
   m_smbinf.Refresh();
   return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue())));
  }
//------------------------------------------------------------------ DealOpen
ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP)
  {
   double op,sl,tp,apr,StopLvl;
   // determinar los parámetros del precio
   m_smbinf.RefreshRates(); m_smbinf.Refresh();
   StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // recordar el nivel de stop
   apr     = ReversPrice(dir); 
   op      = BasePrice(dir);                         // precio de apertura
   sl      = NormalSL(dir, op, apr, SL, StopLvl);    // stop loss
   tp      = NormalTP(dir, op, apr, TP, StopLvl);    // take profit

   // posición abierta
   m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp);
   ulong order = m_trade.ResultOrder(); 
   if(order<=0) return(0);                           // ticket de orden
   return(GetDealByOrder(order));                    // devolver el ticket de transacción
  }
//------------------------------------------------------------------ GetDealByOrder
ulong CExpertAdvisor::GetDealByOrder(ulong order) // obtener el ticket de transacción mediante el ticket de orden
  {
   PositionSelect(m_smb);
   HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER));
   uint total=HistoryDealsTotal();
   for(uint i=0; i<total; i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(order==HistoryDealGetInteger(deal,DEAL_ORDER))
         return(deal);                            // recordar el ticket de transacción
     }
   return(0);
  }
//------------------------------------------------------------------ CountProfit
double CExpertAdvisor::CountProfitByDeal(ulong ticket)  // ganancia de la posición mediante ticket de transacción
  {
   CDealInfo deal; deal.Ticket(ticket);                 // ticket de transacción
   HistorySelect(deal.Time(),TimeCurrent());            // seleccionar todas las transacciones después de esta
   uint total  = HistoryDealsTotal();
   long pos_id = deal.PositionId();                     // obtener el identificador de la transacción
   double prof = 0;
   for(uint i=0; i<total; i++)                          // buscar todas las transacciones con este identificador
     {
      ticket = HistoryDealGetTicket(i);
         if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue;
      prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // resumir el beneficio
     }
   return(prof);                                        // devolver el beneficio
  }

4. Módulos de trading

Por último, este grupo de funciones enlaza todo el proceso de trading, procesando los eventos mediante las funciones de servicio y las macros. Hay pocas unidades lógicas de operaciones de trading, dependen de los objetivos que hayas definido. Sin embargo, podemos distinguir los conceptos comunes, que existen en todos los expertos.

   virtual bool      Main();                            // módulo principal de control del proceso de trading
   virtual void      OpenPosition(long dir);            // módulo de apertura de posiciones
   virtual void      CheckPosition(long dir);           // comprobar posiciones y abrir otras
   virtual void      ClosePosition(long dir);           // cerrar posiciones
   virtual void      BEPosition(long dir,int BE);       // mover el Stop Loss al punto muerto
   virtual void      TrailingPosition(long dir,int TS); // Trailing de posiciones Stop Loss
   virtual void      OpenPending(long dir);             // módulo de apertura de órdenes pendientes
   virtual void      CheckPending(long dir);            // trabajar con las órdenes actuales y abrir otras
   virtual void      TrailingPending(long dir);         // mover las órdenes pendientes
   virtual void      DeletePending(long dir);           // borrar las órdenes pendientes

En los siguientes ejemplos, consideraremos unas implementaciones concretas de estas funciones.

Añadir nuevas funciones no va a resultar difícil, ya que hemos elegido el enfoque correcto y elaborado la estructura del experto. Si vas a utilizar exactamente este esquema, tus diseños van a requerir unos esfuerzos y tiempo mínimo, y se podrá leer el código incluso después de un año.

Por supuesto, tus expertos no se limitan a ellas. En la clase CExpertAdvisor hemos añadido sólo los métodos más necesarios. Puedes añadir nuevos controladores a las clases heredadas, modificar las existentes, ampliar tus propios módulos, creando así una única librería. Disponer de tal librería, el desarrollo de expertos "llave en mano" llevaría de media hora a dos días.


4. Ejemplos de uso de la clase CExpertAdvisor

4.1. Ejemplo de funcionamiento en función de las señales del indicador

Como primer ejemplo, vamos a empezar con la tarea más sencilla -considera el Expert Advisor MovingAverage (ejemplo básico de MetaTrader 5) mediante la clase CExpertAdvisor. Vamos a complicarlo un poco.

Algoritmo:

a) Condición para la apertura de una posición

  • Si el precio cruza el MA de abajo hacia arriba, entonces abre la posición para Comprar.
  • Si el precio cruza el MA de arriba hacia abajo, entonces abre la posición para Vender.
  • Establecer los valores de SL (Stop Loss), TP (TakeProfit).
  • Se calcula el lote de la posición mediante el parámetro Risk -cuánto perderá el depósito cuando se activa el Stop Loss.

b) Condición para el cierre de una posición

  • Si el precio cruza el MA de abajo hacia arriba, entonces cierra la posición para Vender.
  • Si el precio cruza el MA de arriba hacia abajo, entonces cierra la posición para Comprar.

c) Limitación

  • Poner límites al tiempo de funcionamiento de un experto diariamente, desde HourStart hasta HourEnd.
  • El experto realiza operaciones de trading sólo con la aparición de una nueva barra.

d) Asistencia de posición

  • Utilizar simplemente un Trailing Stop a una distancia de TS.

Para nuestro experto, vamos a necesitar siete funciones de la clase CExpertAdvisor:

  • Función de señal -CheckSignal()  
  • Filtro de ticks -CheckNewBar()
  • Filtro de tiempo -CheckTime()
  • Función de servicio para abrir posiciones -DealOpen()
  • Tres módulos de trabajo -OpenPosition(), ClosePosition(), TrailingPosition()

La función CheckSignal() y los módulos se deben definir en una clase heredada para resolver sus tareas concretas. También hay que añadir la inicialización del indicador.

//+------------------------------------------------------------------+
//|                                              Moving Averages.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"

input double Risk      = 0.1; // Riesgo
input int    SL        = 100; // Distancia Stop Loss
input int    TP        = 100; // Distancia Take Profit
input int    TS        =  30; // Distancia Trailing Stop
input int    pMA       =  12; // Período Moving Average
input int    HourStart =   7; // Hora de inicio del trading
input int    HourEnd   =  20// Hora de finalización del trading
//---
class CMyEA : public CExpertAdvisor
  {
protected:
   double            m_risk;          // volumen del riesgo
   int               m_sl;            // Stop Loss
   int               m_tp;            // Take Profit
   int               m_ts;            // Trailing Stop
   int               m_pMA;           // período MA
   int               m_hourStart;     // Hora de inicio del trading
   int               m_hourEnd;       // Hora de finalización del trading
   int               m_hma;           // indicador MA
public:
   void              CMyEA();
   void             ~CMyEA();
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // inicialización
   virtual bool      Main();                              // función principal
   virtual void      OpenPosition(long dir);              // abrir posición con la señal
   virtual void      ClosePosition(long dir);             // cerrar posición con la señal
   virtual long      CheckSignal(bool bEntry);            // comprobar la señal
  };
//------------------------------------------------------------------ CMyEA
void CMyEA::CMyEA() { }
//----------------------------------------------------------------- ~CMyEA
void CMyEA::~CMyEA()
  {
   IndicatorRelease(m_hma); // eliminar el indicador MA
  }
//------------------------------------------------------------------ Init
bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false);  // inicializar la clase principal

   m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA;  // copiar los parámetros
   m_hourStart=HourStart; m_hourEnd=HourEnd;

   m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // crear un indicador MA
   if(m_hma==INVALID_HANDLE) return(false);            // salir en caso de error
   m_bInit=true; return(true);                         // trading permitido
  }
//------------------------------------------------------------------ Main
bool CMyEA::Main()                            // función principal
  {
   if(!CExpertAdvisor::Main()) return(false); // llamar a la función de la clase principal

   if(Bars(m_smb,m_tf)<=m_pMA) return(false); // si hay un número insuficiente de barras
   
   if(!CheckNewBar()) return(true);           // buscar barras nuevas

   // comprobar cada dirección
   long dir;
   dir=ORDER_TYPE_BUY;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);
   dir=ORDER_TYPE_SELL;
   OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts);

   return(true);
  }
//------------------------------------------------------------------ OpenPos
void CMyEA::OpenPosition(long dir)
  {
   if(PositionSelect(m_smb)) return;     // si hay una orden, entonces salir
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00"))) return;
   if(dir!=CheckSignal(true)) return;    // si no hay ninguna señal para la dirección actual
   double lot=CountLotByRisk(m_sl,m_risk,0);
   if(lot<=0) return;                    // salir si no está definido el lote
   DealOpen(dir,lot,m_sl,m_tp);          // abrir posición
  }
//------------------------------------------------------------------ ClosePos
void CMyEA::ClosePosition(long dir)
  {
   if(!PositionSelect(m_smb)) return;                 // salir si no hay ninguna posición
   if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"),
                 StringToTime(IntegerToString(m_hourEnd)+":00")))
     { m_trade.PositionClose(m_smb); return; }        // cerrar las órdenes si no es el intervalo de tiempo del trading
   if(dir!=PositionGetInteger(POSITION_TYPE)) return; // si no se puede comprobar la dirección de la posición
   if(dir!=CheckSignal(false)) return;                // si la señal de cierre no coincide con la posición actual
   m_trade.PositionClose(m_smb,1);                    // cerrar posición
  }
//------------------------------------------------------------------ CheckSignal
long CMyEA::CheckSignal(bool bEntry)
  {
   MqlRates rt[2];
   if(CopyRates(m_smb,m_tf,0,2,rt)!=2)
     { Print("CopyRates ",m_smb," no está cargado el historial"); return(WRONG_VALUE); }

   double ma[1];
   if(CopyBuffer(m_hma,0,0,1,ma)!=1)
     { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); }

   if(rt[0].open<ma[0] && rt[0].close>ma[0])
      return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condición para comprar
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condición para vender

   return(WRONG_VALUE);                                // si no hay ninguna señal
  }

CMyEA ea; // instancia de clase
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // inicializar el experto
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------ OnTick
void OnTick()
  {
   ea.Main();                  // procesar el tick entrante
  }

Vamos a analizar la estructura de la función Main(). Se suele dividir en dos partes.

En la primera parte se llama a la función principal. Esta función procesa los posibles parámetros que afectan el funcionamiento de un experto de manera global. Estas incluyen la comprobación de la asignación de permiso al experto para hacer trading y la validación de los datos en el historial.

En la segunda parte se procesan los eventos del mercado directamente.

Se prueba el filtro CheckNewBar() -comprobar si hay una barra nueva. Y se llama a los módulos para las dos direcciones del trading uno tras otro.

Todo está organizado de manera bastante abstracta en los módulos (segundo principio del diseño). No hay un acceso directo a las propiedades del símbolo. Y tres módulos -OpenPosition(), ClosePosition() y TrailingPosition() -dependen únicamente de estos parámetros que les llegan desde el exterior. Esto te servirá para llamar a estos módulos para comprobar las órdenes, tanto de Compra como de Venta. 


4.2. Ejemplo de uso de CExpertAdvisor -Experto sin indicadores, analizando el estado de la posición y el resultado

Para este ejemplo, vamos a tomar el sistema que hace trading en la posición opuesta con mucho incremento después de la pérdida (a este tipo de expertos se le suele llamar "Martingale").

a) Colocar una orden inicial

  •  cuando el experto inicia la compra, abre la primera posición para Comprar con un lote inicial.

b) Abrir posiciones posteriores

  • Si se cierra la posición anterior con ganancia, se abre una posición en la misma dirección con el primer lote inicial.
  • Si se cierra la posición anterior con pérdida, entonces se abre una posición en la dirección opuesta con un lote superior (mediante un factor).

Para nuestro experto, vamos a necesitar tres funciones de la clase CExpertAdvisor:

  • Abrir posición -DealOpen().
  • Obtener el valor de la ganancia de la posición cerrada mediante el ticket de transacción -CountProfitByDeal().
  • Módulos de trabajo -OpenPosition(), CheckPosition().

Puesto que el experto analiza sólo los resultados de las transacciones, pero ningún indicador, usaremos los eventos OnTrade() para un máximo rendimiento. Es decir, el experto que ha colocado la primera orden inicial para la Compra, colocará todas las órdenes posteriores, pero sólo después de cerrar esta posición. Así que vamos a colocar la primera orden en OnTick() y el trabajo posterior en OnTrade().

Como siempre, la función Init() sólo inicializa los parámetros de la clase con los parámetros externos del experto.

El módulo OpenPosition() abre la posición inicial y se bloquea mediante el flag m_first.

El módulo CheckPosition() controla los demás cambios de posición.

Se llama a estos módulos desde las respectivas funciones del experto: OnTick() y OnTrade().

//+------------------------------------------------------------------+
//|                                                       eMarti.mq5 |
//|              Copyright Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include "ExpertAdvisor.mqh"
#include <Trade\DealInfo.mqh>

input double Lots    = 0.1; // Lote
input double LotKoef = 2;   // multiplicador de lote para pérdidas
input int    Dist    = 60;  // distancia al Stop Loss y Take Profit
//---
class CMartiEA : public CExpertAdvisor
  {
protected:
   double            m_lots;       // Lot
   double            m_lotkoef;    // multiplicador de lote para pérdidas
   int               m_dist;       // distancia al Stop Loss y Take Profit
   CDealInfo         m_deal;       // última transacción
   bool              m_first;      // flag para la apertura de la primera posición
public:
   void              CMartiEA() { }
   void             ~CMartiEA() { }
   virtual bool      Init(string smb,ENUM_TIMEFRAMES tf); // inicialización
   virtual void      OpenPosition();
   virtual void      CheckPosition();
  };
//------------------------------------------------------------------ Init
bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf)
  {
   if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // inicializar la clase principal
   m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist;       // copiar los parámetros
   m_deal.Ticket(0); m_first=true;
   m_bInit=true; return(true);                        // trading permitido
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::OpenPosition()
  {
   if(!CExpertAdvisor::Main()) return;                       // llamar a la función principal
   if(!m_first) return;                                      // si la posición inicial ya está abierta
   ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // abrir posición inicial
   if(deal>0) { m_deal.Ticket(deal); m_first=false; }        // si existe la posición
  }
//------------------------------------------------------------------ OnTrade
void CMartiEA::CheckPosition()
  {
   if(!CExpertAdvisor::Main()) return;           // llamar a la función principal
   if(m_first) return;                           // si no está colocada la posición inicial
   if(PositionSelect(m_smb)) return;             // si la posición existe

   // comprobar la ganancia de la posición anterior
   double lot=m_lots;                            // lote inicial
   long dir=m_deal.Type();                       // dirección anterior
   if(CountProfitByDeal(m_deal.Ticket())<0)      // si han habido pérdidas
     {
      lot=NormalLot(m_lotkoef*m_deal.Volume());  // aumentar el lote
      dir=ReversType(m_deal.Type());             // posición opuesta
     }
   ulong deal=DealOpen(dir,lot,m_dist,m_dist);   // abrir posición
   if(deal>0) m_deal.Ticket(deal);               // recordar el ticket
  }

CMartiEA ea; // instancia de clase
//------------------------------------------------------------------ OnInit
int OnInit()
  {
   ea.Init(Symbol(),Period()); // inicializar el experto
   return(0);
  }
//------------------------------------------------------------------ OnDeinit
void OnDeinit(const int reason) { }
//------------------------------------------------------------------OnTick
void OnTick()
  {
   ea.OpenPosition();          // procesar el tick - abrir la primera orden
  }
//------------------------------------------------------------------ OnTrade
void OnTrade()
  {
   ea.CheckPosition();         // procesar el evento de trading
  }


5. Trabajar con eventos

En este artículo has visto ejemplos de procesamiento de dos eventos - NewTick y Trade representados con las funciones OnTick() y OnTrade() respectivamente. En la mayoría de los casos, se usan constantemente estos dos eventos.

Hay cuatro funciones de procesamiento de eventos para los expertos:

  • OnChartEvent procesa un amplio grupo de eventos: cuando se trabaja con objetos gráficos, teclado, ratón y eventos personalizados. Por ejemplo, se utiliza la función para crear expertos interactivos o expertos basados en el principio de la administración gráfica de las órdenes. O simplemente para crear crear controles activos e los parámetros del programa MQL (mediante botones y campos de edición). En general, se usa esta función para procesar los eventos externos de un experto.
  • OnTimer, se le llama cuando se procesa un evento del temporizador del sistema. Se utiliza los siguientes casos: cuando el programa MQL necesita analizar su entorno de manera regular, para calcular los valores de los indicadores, cuando hace falta para referirse continuamente a las fuentes externas de señales, etc. En términos generales, la función OnTimer() es una alternativa ,incluso el mejor sustituto de:
    while(true) {  /* llevar a cabo el análisis */; Sleep(1000); }. 
    Es decir, el experto no tiene que trabajar en un bucle infinito en su inicio, pero lo suficiente para llamar a sus funciones desde OnTick() hasta OnTimer().
  • OnBookEvent procesa un evento generado cuando cambia el estado de la Profundidad del mercado. Este evento puede proceder del exterior y llevar a cabo su procesamiento según la tarea.
  • OnTester, se le llama después de probar el experto en determinado intervalo de fechas, antes de la función OnDeinit() para una posible revisión de las pruebas generadas, al utilizar la optimización genética mediante el parámetro Custom max.

No olvides que es siempre aconsejable utilizar los eventos o las combinaciones de los mismos para solucionar tareas concretas.


Epílogo

Como puedes ver, la escritura de un experto, cuando se dispone del esquema adecuado, no requiere mucho tiempo. Debido a las nuevas posibilidades de procesamiento de eventos en MQL5, disponemos de una estructura más flexible de administración de procesos de trading. Pero todo esto se convierte en una herramienta muy potente si preparas tus algoritmos de trading de manera correcta.

Este artículo describe los tres principios fundamentales para su creación -acontecibilidad, abstracción, modularidad. Vas a llevar a cabo tu trading de manera más fácil, si construyes tus expertos basándote en estos "tres pilares".

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

Archivos adjuntos |
emarti.mq5 (7.49 KB)
emyea.mq5 (12.29 KB)
expertadvisor.mqh (37.78 KB)
Sobre nuevos raíles: Indicadores personalizados en MQL5 Sobre nuevos raíles: Indicadores personalizados en MQL5
No voy a enumerar todas las posibilidades y características de la nueva terminal y el lenguaje. Son numerosas y algunas de ellas merecen ser tratadas en un artículo separado. Tampoco hay aquí código escrito en lenguaje de programación orientado a objeto. Es un tema demasiado importante como para ser tan solo mencionado como ventaja adicional para los programadores. En este artículo vamos a ver los indicadores, su estructura, diseño, tipos y detalles de su programación al compararlos con MQL4. Espero que este artículo sea de utilidad tanto para principiantes como para programadores experimentados. Puede que algunos de estos últimos encuentren algo nuevo.
20 señales de trading en MQL5 20 señales de trading en MQL5
En este artículo aprenderás a recibir las señales de trading necesarias para el funcionamiento de un sistema de trading. Los ejemplos de formación de las 20 señales de trading se dan aquí como funciones personalizadas separadas, que se pueden utilizar durante el desarrollo de los Expert Advisors. Para tu comodidad, todas las funciones utilizadas en este artículo están incluidas en un archivo mqh, que se puede conectar fácilmente a un futuro Expert Advisor.
Jeremy Scott es un vendedor de gran éxito en el mercado MQL5 Jeremy Scott es un vendedor de gran éxito en el mercado MQL5
Jeremy Scott, más conocido en MQL5.community con el nick Johnnypasado, ha adquirido fama en el terreno de nuestro servicio de mercado MQL5. Ya ha ganado varios miles de dólares en el Mercado y este no es el límite, ni mucho menos. Hemos decidido estudiar atentamente al futuro millonario y preguntarle el secreto del éxito para los vendedores del mercado MQL5.
Cómo escribir un indicador basado en otro indicador Cómo escribir un indicador basado en otro indicador
En MQL5 puedes escribir un indicador desde cero o a partir de otro indicador que ya existe, integrado en el terminal o personalizado. Aquí también hay dos maneras -para mejorar el indicador añadiéndole cálculos y estilos gráficos, o utilizar un indicador integrado en el terminal de cliente o uno personalizado mediante las funciones iCustom() o IndicatorCreate()