Descargar MetaTrader 5

Experto comercial universal: Modelo de eventos y prototipo de estrategia comercial (Parte 2)

11 febrero 2016, 08:35
Vasiliy Sokolov
0
689

Contenido

 

Introducción

Este artículo continúa la presentación a los lectores del motor comercial universal CStrategy. En el primer artículo Experto comercial universal: Los modos comerciales de las estrategias (Parte 1) se analizaron con detalle los modos comerciales y las funciones que los organizan. Se propuso un esquema universal de expertos, que consta de cuatro métodos, dos de los cuales abren nuevas posiciones, mientras que los otros dos las cierran. Una combinación diversa en la llamada de estos métodos determina la presencia de uno u otro modo comercial, por ejemplo, para un experto así se puede establecer un modo solo de compras o solo de ventas, el acompañamiento de las posiciones abiertas con anterioridad o el modo espera. Gracias a estos modos, el experto adquiere la posibilidad de ajustar de manera flexible su funcionamiento, dependiendo de la hora comercial o incluso del día de la semana.

Sin embargo, los modos comerciales no son, ni mucho menos, lo único que puede necesitar un experto comercial. En esta segunda parte de la serie de artículos, hablaremos sobre el modelo de eventos del motor comercial CStrategy, basado en el procesamiento centralizado de eventos. El esquema de procesamiento propuesto se distingue de los eventos de sistema precisamente en que todos los eventos se reúnen en un sitio. Hablaremos más abajo de las ventajas que proporciona tal organización.

Asimismo, en esta artículo se describen las dos clases más importantes de todo el motor comercial: son las clases CStrategy y CPosition. La primera de ellas constituye el núcleo de la lógica comercial del experto, ya que une los eventos y los modos en una única carcasa flexible, que heredará directamente el experto del usuario. La segunda clase es la base de la operaciones comerciales universales. En él se ubican las acciones con las posiciones abiertas (por ejemplo, el cierre de posición o el cambio de su nivel de Stop Loss o Take Profit). Gracias a esto, todas las acciones comerciales se convierten en acciones de un solo tipo e independientes de la plataforma.

Resulta necesario comprender que las peculiaridades del trabajo con el motor, que obligatoriamente se deberán respetar durante su uso, han sido creadas a fin de cuentas por el bien del propio usuario. Por ejemplo, la iteración habitual de posiciones y el acceso a los eventos de sistema para la estrategia no son accesibles. Gracias a ello, no deberemos preocuparnos por la secuencia de las acciones, ni tampoco por el manejador en el que deberemos procesar tal o cual evento. En lugar de esto, CStrategy propone al experto de usuario concentrarse solo en su lógica comercial, asumiendo la ejecución de estas y otras muchas operaciones.

Los motores comerciales similares a los descritos aquí, se crean sobre todo para que los usuarios normales puedan crear sin esfuerzo los funcionales que necesitan. No hará falta que usted comprenda hasta el último entresijo de cada uno de los algoritmos descritos. Bastará con comprender los principios generales y el funcional CStrategy existente. Por eso, si durante la lectura usted encuentra algunos lugares bastante difíciles de entender, sálteselos sin ningún reparo.

 

Modelo de eventos basado en el procesamiento centralizado de datos, enumeración ENUM_MARKET_EVENT_TYPE

MetaTrader 5 proporciona bastantes eventos. Entre estos eventos, existen tanto aquellos que notifican un cambio en el precio de mercado (NewTick, BookEvent), como eventos de sistema del tipo Timer o TradeTransaction. En cada uno de estos eventos existe una función de sistema homónima con el prefijo On*. Dicha función es el operador de este evento. Por ejemplo, si se necesita procesar la llegada de un nuevo tick, será necesario ubicar el conjunto necesario de procedimientos en la función OnTick:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Procesamos la llegada de un nuevo tick
   // ...
  }

Al llegar el evento OnBookEvent, se propone llamar otro bloque de código, que es responsable del procesamiento del cambio de precio en la profundidad de mercado:

void OnBookEvent (const string& symbol)
  {
   // Aquí procesamos el cambio de precio en la profundidad de mercado
   // ...
  }

Con este enfoque, cuantos más eventos procese nuestro experto, más fragmentaria se hará su lógica. Con un enfoque así, las condiciones de apertura o cierre de posición pueden ser dispersadas por diferentes bloques. Desde el punto de vista de la programación, la dispersión por bloques puede ser una buena solución, aunque en este caso, semejante enfoque no sería deseable. Al contrario, es mejor concentrar toda la lógica comercial en un lugar especialmente realizado para ello.

Además, algunos eventos en MetaTrader 5 no tienen soporte. En este caso, al experto se le propone de forma automática determinar la aparición de unas u otras condiciones. Por ejemplo, en MetaTrader 5 no hay evento que procese la apertura de una nueva barra, mientras que se trata de la comprobación más utilizada que realizan los expertos. Por eso el modelo de eventos del motor comercial descrito da soporte, no solo a los eventos de sistema, sino también a los eventos de usuario (no confundir con los eventos de usuario en el gráfico), que simplifican significativamente la escritura de expertos comerciales. Por ejemplo, a estos eventos pertenece la creación de una nueva barra en el gráfico.

Para aclararnos con el modelo de eventos supuesto, vamos a describir los eventos relacionados con el cambio de precio o de hora con la ayuda de la enumeración especial ENUM_MARKET_EVENT_TYPE:

//+------------------------------------------------------------------+
//| Determina el tipo de evento de mercado.                          |
//+------------------------------------------------------------------+
enum ENUM_MARKET_EVENT_TYPE
  {
   MARKET_EVENT_TICK,               // Llegada de un nuevo tick
   MARKET_EVENT_BAR_OPEN,           // Apertura de una nueva barra
   MARKET_EVENT_TIMER,              // Activación del temporizador
   MARKET_EVENT_BOOK_EVENT          // Cambio de la profundidad de mercado (incluida  la llegada de un nuevo tick).
  };

Como puede ver, la enumeración contiene la descripción tanto de eventos de sistema, como de los eventos a los que MetaTrader 5 no da soporte directamente (MARKET_EVENT_BAR_OPEN — la apertura de una nueva barra según un instrumento de trabajo del experto).

Supongamos que nuestro experto universal tenga cuatro métodos de lógica comercial: InitBuy, InitSell, SupportBuy, SupportSell. Sobre estos métodos ya escribimos en la primera parte del artículo "El experto comercial universal". Si en calidad de uno de los parámetros de estos métodos usamos uno de los valores de esta enumeración, la lógica de procesamiento del experto siempre podrá saber en base a qué evento se ha realizado la llamada del método actual. Vamos a describir el esquema de este experto:

//+------------------------------------------------------------------+
//|                                                     ExampExp.mq5 |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#property version   "1.00"
#include <Strategy\Series.mqh>
ulong ExpertMagic=12345; // Número mágico del experto
//+------------------------------------------------------------------+
//| Determina el tipo de evento de mercado.                          |
//+------------------------------------------------------------------+
enum ENUM_MARKET_EVENT_TYPE
  {
   MARKET_EVENT_TICK,               // Llegada de un nuevo tick según el instrumento actual
   MARKET_EVENT_BAR_OPEN,           // Apertura de una nueva barra según el instrumento actual
   MARKET_EVENT_TIMER,              // Activación del temporizador
   MARKET_EVENT_BOOK_EVENT          // Cambio de la profundidad de mercado (incluida la llegada del tick).
  };
//+------------------------------------------------------------------+
//| Prototipo del experto universal.                                 |
//+------------------------------------------------------------------+
class CExpert
  {
public:
   void              InitBuy(ENUM_MARKET_EVENT_TYPE &event_id);
   void              InitSell(ENUM_MARKET_EVENT_TYPE &event_id);
   void              SupportBuy(ENUM_MARKET_EVENT_TYPE &event_id);
   void              SupportSell(ENUM_MARKET_EVENT_TYPE &event_id);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpert::InitBuy(ENUM_MARKET_EVENT_TYPE &event_id)
  {
   printf(__FUNCTION__+" EventID: "+EnumToString(event_id));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpert::InitSell(ENUM_MARKET_EVENT_TYPE &event_id)
  {
   printf(__FUNCTION__+" EventID: "+EnumToString(event_id));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpert::SupportBuy(ENUM_MARKET_EVENT_TYPE &event_id)
  {
   printf(__FUNCTION__+" EventID: "+EnumToString(event_id));
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CExpert::SupportSell(ENUM_MARKET_EVENT_TYPE &event_id)
  {
   printf(__FUNCTION__+" EventID: "+EnumToString(event_id));
  }

ENUM_MARKET_EVENT_TYPE event_type;
CExpert Expert;
datetime last_time;
CTime Time;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   EventSetTimer(1);
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   event_type=MARKET_EVENT_TICK;
   CallExpertLogic(event_type);
   if(last_time!=Time[0])
     {
      event_type=MARKET_EVENT_BAR_OPEN;
      CallExpertLogic(event_type);
      last_time=Time[0];
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnTimer()
  {
   event_type=MARKET_EVENT_TIMER;
   CallExpertLogic(event_type);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
   event_type=MARKET_EVENT_TIMER;
   CallExpertLogic(event_type);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CallExpertLogic(ENUM_MARKET_EVENT_TYPE &event)
  {
   Expert.InitBuy(event);
   Expert.InitSell(event);
   Expert.SupportBuy(event);
   Expert.SupportSell(event);
  }
//+------------------------------------------------------------------+

Los eventos que anuncian los cambios de precio en el mercado, llaman a la misma función, CallExpertMagic, de la que se transmite como parámetro el evento actual. Esta función, a su vez, ejecuta la llamada de los cuatro métodos del experto CExpert. Al llamar cada uno de esots métodos, escribe su nombre y el identificador del evento gracias al que ha sido llamado. Si iniciamos este asesor de prueba en el gráfico, transcurrido un tiempo, aparecerán las entradas correspondientes, que avisarán de que los identificadores de los eventos han sido procesados:

2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:40.015 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_TICK
2015.11.30 14:13:40.015 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_TICK
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_BAR_OPEN
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_TICK
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_TICK
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_TICK
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_TICK

 

Acceso a los eventos sucedidos en otros instrumentos, estructura MarketEvent

Al proyectar un sistema comercial que analiza a la vez varios instrumentos, normalmente es necesario un mecanismo que realice el seguimiento del cambio de los precios en varios instrumentos. Sin embargo, la función estándar OnTick se llama solo en el momento de llegada de un nuevo tick en el instrumento en el que ha sido iniciado el experto. Por otra parte, a la disposición de los manejadores de los sistemas comerciales se encuentra la función OnBookEvent. A diferencia de OnTick, la función OnBookEvent se invoca al cambiar la profundidad de mercado del instrumento al que se realizó la suscripción con la ayuda de la función MarketBookAdd.

El cambio de la profundidad de mercado es un evento muy frecuente, y por eso el seguimiento de semejante evento es un procedimiento que exige el gasto de muchos recursos. Normalmente, al experto le basta con realizar un seguimiento de los cambios del flujo de ticks en otros instrumentos. Por otra parte, el evento de cambio en la profundidad de mercado incluye también la llegada de un nuevo tick. Aparte de OnBookEvent, se puede ajustar con cierta periodicidad la llamada OnTimer y ya analizar en esta función los precios cambiados de varios instrumentos.

De esta forma, en las funciones de sistema que reaccionan a los eventos NewTick, BookEvent y Timer, se puede ubicar la llamada de un cierto módulo intermedio (lo llamaremos de forma convencional EventProcessor), que a su vez analizaría el cambio de los precios en varios instrumentos simultáneamente y generaría este u otro evento. Cada evento tendría una descripción unificada en forma de estructura y se enviaría a los métodos de gestión de la estrategia. La estrategia, tras recibir el evento correspondiente en forma de estructura, reaccionaría a él o lo dejaría pasar. En este caso, además, la función de sistema, que sería prácticamente la iniciadora de este evento para el experto final, permanecería desconocida.

En realidad, si el experto ha recibido una notificación sobre la llegada de un nuevo tick, entonces da igual cómo haya sido recibida la información: si a través de OnTick, OnTimer o OnBookEvent. Solo importa que el nuevo tick para el instrumento indicado haya llegado. El manejador de eventos puede ser uno para varias estrategias. Por ejemplo, si cada estrategia la presentamos en forma de clase de usuario, entonces se pueden guardar de golpe varios ejemplares de esta clase en una lista especial de estrategias. Además, cada una de las estrategias de la lista tendría la posibilidad de recibir un nuevo evento, generado por el módulo EventProcessor. Presentamos de forma esquemática el modelo de creación y distribución de eventos en el siguiente gráfico:

Fig. 1. Esquema del modelo de creación y distribución de eventos

Ahora solo nos queda por ver la propia estructura que se transmitirá a los expertos en calidad de evento. Esta estructura se llama MarketEvent, y su definición es la siguiente:

//+------------------------------------------------------------------+
//| Estructura que determina el tipo de evento, el instrumento en    |
//| el que ha aparecido, y el marco temporal (para el evento BarOpen)|
//+------------------------------------------------------------------+
struct MarketEvent
  {
   ENUM_MARKET_EVENT_TYPE type;     // Tipo de evento.
   ENUM_TIMEFRAMES   period;        // Periodo del gráfico al que se refiere el evento (solo para MARKET_EVENT_BAR_OPEN).
   string            symbol;        // Nombre del instrumento del que ha tenido lugar el evento.
  };

Tras obtener el ejemplar de esta estructura mediante un enlace, el método que toma la decisión comercial podrá analizarla y tomar la decisión correcta, basándose en la siguiente información:

  • tipo de evento;
  • marco temporal del gráfico en el que ha sucedido este evento;
  • nombre del instrumento del que ha tenido lugar el evento.

En los eventos para los que no tiene sentido analizar el periodo del gráfico (por ejemplo, los eventos NewTick o Timer), el campo period de la estructura MarketEvent siempre estará relleno con el valor PERIOD_CURRENT.

 

El evento "nueva barra". Algoritmos de determinación de nuevos ticks y barras

Para realizar un seguimiento de la llegada de los nuevos ticks y la formación de nuevas barras en varios instrumentos a la vez, es necesario escribir los módulos-clases correspondientes, que realizarán este trabajo. Estos módulos son partes internas de la clase CStrategy, y el usuario no interactúa directamente con ellos. El primer módulo que estudiaremos, será la clase CTickDetector. El código fuente de esta clase se muestra más abajo:

//+------------------------------------------------------------------+
//|                                              NewTickDetector.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Definidor del nuevo tick                                         |
//+------------------------------------------------------------------+
class CTickDetector : public CObject
  {
private:
   string            m_symbol;         // Símbolo del que se necesita seguir la llegada de un nuevo tick.
   MqlTick           m_last_tick;      // Último tick guardado.
public:
                     CTickDetector(void);
                     CTickDetector(string symbol);
   string            Symbol(void);
   void              Symbol(string symbol);
   bool              IsNewTick(void);
  };
//+------------------------------------------------------------------+
//| El constructor establece el marco temporal y el instrumento      |
//| actual por efecto.                                               |
//+------------------------------------------------------------------+
CTickDetector::CTickDetector(void)
  {
   m_symbol=_Symbol;
  }
//+------------------------------------------------------------------+
//| Crea un objeto con el símbolo y marco temporal preestablecido.   |
//+------------------------------------------------------------------+
CTickDetector::CTickDetector(string symbol)
  {
   m_symbol=symbol;
  }
//+------------------------------------------------------------------+
//| Establece el nombre del instrumento en el que hay que seguir     |
// | una nueva barra.                                                |
//+------------------------------------------------------------------+
void CTickDetector::Symbol(string symbol)
  {
   m_symbol=symbol;
  }
//+------------------------------------------------------------------+
//| Retorna el nombre del instrumento en el que hay que seguir       |
//| una nueva barra.                                                 |
//+------------------------------------------------------------------+
string CTickDetector::Symbol(void)
  {
   return m_symbol;
  }
//+------------------------------------------------------------------+
// | Devuelve true si en el instrumento y marco temporal indicado    |
//| ha aparecido un nuevo tick.                                      |
//+------------------------------------------------------------------+
bool CTickDetector::IsNewTick(void)
  {
   MqlTick tick;
   SymbolInfoTick(m_symbol,tick);
   if(tick.last!=m_last_tick.last || 
      tick.time!=m_last_tick.time)
     {
      m_last_tick=tick;
      return true;
     }
   return false;
  }

Su principal método de trabajo es el método IsNewTick. Retorna verdadero si se ha recibido un nuevo tick del instrumento seguido. El propio instrumento que se necesita supervisar, se establece mediante el método Symbol. La clase CTickDetector se hereda de CObject y por lo tanto puede ser colocado como un elemento de la colección CArrayObj. Eso es lo que necesitamos. Por ejemplo, puede crear diez ejemplares de CTickDetect, cada uno de los cuales deberá designar su símbolo para el monitoreo. Requiriendo de forma consecutiva a la colección de estas clases del tipo CArrayObj, es posible saber en qué instrumento se forma un nuevo tick, y después generar el evento correspondiente, que transmita esta información a la colección de expertos.

Como ya se mencionó, además de nuevos ticks, con mucha frecuencia es necesario determinar la llegada de nuevas barras. Lo mejor es delegar esta tarea en la clase especial CBarDetector. Funciona más o menos de la misma forma que la clase CTickDetector. El método básico de CBarDetector es IsNewBar, el método devuelve true si ha llegado una nueva barra de un instrumento cuyo nombre se necesita establecer de forma preliminar con la ayuda del método Symbol. Aquí está el código fuente de esta clase:

//+------------------------------------------------------------------+
//|                                               NewBarDetecter.mqh |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include <Object.mqh>
//+------------------------------------------------------------------+
//| Clase que detecta la aparición de una nueva barra en             |
//| el instrumento y periodo indicado.                               |
//+------------------------------------------------------------------+
class CBarDetector : public CObject
  {
private:
   ENUM_TIMEFRAMES   m_timeframe;      // Marco temporal conforme al que se debe realizar el seguimiento de una nueva barra
   string            m_symbol;         // Símbolo conforme al que se debe realizar el seguimiento de una nueva barra
   datetime          m_last_time;      // hora de la última barra conocida
public:
                     CBarDetector(void);
                     CBarDetector(string symbol,ENUM_TIMEFRAMES timeframe);
   void              Timeframe(ENUM_TIMEFRAMES tf);
   ENUM_TIMEFRAMES   Timeframe(void);
   void              Symbol(string symbol);
   string            Symbol(void);
   bool              IsNewBar(void);
  };
//+------------------------------------------------------------------+
//| El constructor establece el marco temporal y el instrumento      |
//| actual por efecto.                                               |
//+------------------------------------------------------------------+
CBarDetector::CBarDetector(void)
  {
   m_symbol=_Symbol;
   m_timeframe=Period();
  }
//+------------------------------------------------------------------+
//| Crea un objeto con el símbolo y marco temporal preestablecido.   |
//+------------------------------------------------------------------+
CBarDetector::CBarDetector(string symbol,ENUM_TIMEFRAMES tf)
  {
   m_symbol=symbol;
   m_timeframe=tf;
  }
//+------------------------------------------------------------------+
//| Establecer el marco temporal en el que se debe realizar          |
//| el seguimiento de una nueva barra.                               |
//+------------------------------------------------------------------+
void CBarDetector::Timeframe(ENUM_TIMEFRAMES tf)
  {
   m_timeframe=tf;
  }
//+------------------------------------------------------------------+
//| Devuelve marco temporal en el que se realiza el seguimiento      |
//| de la formación de una nueva barra.                              |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CBarDetector::Timeframe(void)
  {
   return m_timeframe;
  }
//+------------------------------------------------------------------+
//| Establece el nombre del instrumento, en el que hay que seguir    |
//|la formación una nueva barra.                                     |
//+------------------------------------------------------------------+
void CBarDetector::Symbol(string symbol)
  {
   m_symbol=symbol;
  }
//+------------------------------------------------------------------+
//| Retorna el nombre del instrumento, en el que hay que seguir      |
//| la formación una nueva barra.                                    |
//+------------------------------------------------------------------+
string CBarDetector::Symbol(void)
  {
   return m_symbol;
  }
//+------------------------------------------------------------------+
//| Devuelve true si en el instrumento y marco temporal indicado     |
//| ha aparecido una nueva barra.                                    |
//+------------------------------------------------------------------+
bool CBarDetector::IsNewBar(void)
  {
   datetime time[];
   if(CopyTime(m_symbol, m_timeframe, 0, 1, time) < 1)return false;
   if(time[0] == m_last_time)return false;
   return m_last_time = time[0];
  }

 

Clase CPositionMT5 — la base de un algoritmo independiente de la plataforma

Es hora de examinar una de las clases más importantes que posibilitan el funcionamiento de los expertos universales. Esta clase incluye los métodos para trabajar con una posición en MetaTrader 5. Desde un punto de vista técnico, está ejecutado de una forma muy sencilla. En su esencia, es una clase contenedora, intermediaria entre el experto y las funciones de sistema para trabajar con las posiciones en MetaTrader 5: PositionSelect y PositionGet... Sin embargo, implementa la función más importante en las clases presentadas, su independencia de la plataforma.

Si se analizan con cuidado, todos los módulos descritos anteriormente no utilizan en su composición funciones específicas para alguna plataforma comercial (MetaTrader 4 y MetaTrader 5). Y esto en realidad es así porque MQL4 en sus versiones modernas y MQL5 son, de hecho, el mismo lenguaje de programación con diferentes conjuntos de funciones. Los conjuntos de funciones específicas para estas dos plataformas se concentran principalmente en el área de la gestión de las posiciones de comercio.

Está claro que cualquier experto comercial utiliza las funciones de gestión de estas posiciones. Sin embargo, si en lugar de estas funciones, el experto utiliza un interfaz abstracto único en forma de posiciones de clase, será posible, al menos desde un punto de vista teórico, escribir un robot que se compile tanto para la plataforma MetaTrader 4, como para MetaTrader 5. Además, no sera necesario introducir correcciones de ningún tipo en el código fuente del robot. Todo lo que se requiere es escribir varias clases dobles, que realicen el trabajo con las posiciones. Una de estas clases utilizará las funciones de MetaTrader 4, y la otra, las de MetaTrader 5. Sin embargo, ambas clases proporcionarán el mismo conjunto de métodos para el experto final, posibilitando de esta manera su independencia de la plataforma.

En la práctica, sin embargo, el tema de la creación de un experto independiente de la plataforma es algo más complicado y merece un artículo aparte. En este mismo material, debido a su naturaleza y voluminosas dimensiones, no tocaremos este tema y dejaremos su análisis para otras partes de este artículo.

El segundo argumento en favor de la utilización de una clase especial que represente la posición abierta de la estrategia, radica en el hecho de que la gestión de cada posición tiene lugar individualmente. El motor comercial itera las posiciones de la lista, transmitiendo cada una de ellas a la lógica del experto. De esta forma se alcanza una flexibilidad máxima: el experto comercial controla cada posición de forma individual, lográndose al mismo tiempo describir las reglas para las estrategias que acompañan más de una posición comercial. Por supuesto, en MetaTrader 5 la posición puede ser sólo una, pero en caso de portar el motor comercial a la plataforma MetaTrader 4, esta función se vuelve extremadamente útil.

Bien, vamos a mostrar el código fuente de esta clase. Es muy simple y obvio:

//+------------------------------------------------------------------+
//|                                                  PositionMT5.mqh |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include <Object.mqh>
#include "Logs.mqh"
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//| Clase de posición activa, para las estrategias clásicas          |
//+------------------------------------------------------------------+
class CPositionMT5 : public CObject
  {
private:
   ulong             m_id;                // Identificador único de la posición
   uint              m_magic;             // Identificador único del experto al que pertenece esta posición.
   ENUM_POSITION_TYPE m_direction;        // Dirección de la posición
   double            m_entry_price;       // Precio de entrada en la posición
   string            m_symbol;            // Instrumento del que está abierta la posición
   datetime          m_time_open;         // Hora de apertura
   string            m_entry_comment;     // Comentario de entrada
   bool              m_is_closed;         // Verdadero si la posición se ha cerrado
   CLog*             Log;                 // Registro
   CTrade            m_trade;             // Módulo comercial
public:
                     CPositionMT5(void);
   uint              ExpertMagic(void);
   ulong             ID(void);
   ENUM_POSITION_TYPE Direction(void);
   double            EntryPrice(void);
   string            EntryComment(void);
   double            Profit(void);
   double            Volume(void);
   string            Symbol(void);
   datetime          TimeOpen(void);
   bool              CloseAtMarket(string comment="");
   double            StopLossValue(void);
   bool              StopLossValue(double sl);
   double            TakeProfitValue(void);
   bool              TakeProfitValue(double tp);
  };
//+------------------------------------------------------------------+
//| Inicialización de las propiedades básicas de la posición         |
//+------------------------------------------------------------------+
void CPositionMT5::CPositionMT5(void) : m_id(0),
                                        m_entry_price(0.0),
                                        m_symbol(""),
                                        m_time_open(0)
  {
   m_id=PositionGetInteger(POSITION_IDENTIFIER);
   m_magic=(uint)PositionGetInteger(POSITION_MAGIC);
   m_direction=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
   m_entry_price=PositionGetDouble(POSITION_PRICE_OPEN);
   m_symbol=PositionGetString(POSITION_SYMBOL);
   m_time_open=(datetime)PositionGetInteger(POSITION_TIME);
   m_entry_comment=PositionGetString(POSITION_COMMENT);
   m_trade.SetExpertMagicNumber(m_magic);
  }
//+------------------------------------------------------------------+
//| Retorna la dirección de la posición.                             |
//+------------------------------------------------------------------+
ENUM_POSITION_TYPE CPositionMT5::Direction(void)
  {
   return m_direction;
  }
//+------------------------------------------------------------------+
//| Devuelve el identificador único del experto al que               |
//| pertenece la posición actual.                                    |
//+------------------------------------------------------------------+
uint CPositionMT5::ExpertMagic(void)
  {
   return m_magic;
  }
//+------------------------------------------------------------------+
//| Devuelve el identificador único de la posición.                  |
//+------------------------------------------------------------------+
ulong CPositionMT5::ID(void)
  {
   return m_id;
  }
//+------------------------------------------------------------------+
//| Devuelve el precio de entrada a la posición.                     |
//+------------------------------------------------------------------+
double CPositionMT5::EntryPrice(void)
  {
   return m_entry_price;
  }
//+------------------------------------------------------------------+
//| Devuelve el comentario entrante de la posición activa.           |
//+------------------------------------------------------------------+
string CPositionMT5::EntryComment(void)
  {
   return m_entry_comment;
  }
//+------------------------------------------------------------------+
//| Devuelve el nombre del instrumento del que está abierta la       |
//| posición actual.                                                 |
//+------------------------------------------------------------------+
string CPositionMT5::Symbol(void)
  {
   return m_symbol;
  }
//+------------------------------------------------------------------+
//| Devuelve la hora de apertura de la posición.                     |
//+------------------------------------------------------------------+
datetime CPositionMT5::TimeOpen(void)
  {
   return m_time_open;
  }
//+------------------------------------------------------------------+
//| Devuelve el nivel absoluto de stop-loss para la posición actual. |
//| Si el nivel de stop-loss no ha sido establecido, devuelve 0.0    |
//+------------------------------------------------------------------+
double CPositionMT5::StopLossValue(void)
  {
   if(!PositionSelect(m_symbol))
      return 0.0;
   return PositionGetDouble(POSITION_SL);
  }
//+------------------------------------------------------------------+
//| Establece el nivel absoluto de stop-loss                         |
//+------------------------------------------------------------------+
bool CPositionMT5::StopLossValue(double sl)
  {
   if(!PositionSelect(m_symbol))
      return false;
   return m_trade.Buy(0.0, m_symbol, 0.0, sl, TakeProfitValue(), NULL);
  }
//+------------------------------------------------------------------+
//| Devuelve el nivel absoluto de stop-loss para la posición actual. |
//| Si el nivel de stop-loss no ha sido establecido, devuelve 0.0    |
//+------------------------------------------------------------------+
double CPositionMT5::TakeProfitValue(void)
  {
   if(!PositionSelect(m_symbol))
      return 0.0;
   return PositionGetDouble(POSITION_TP);
  }
//+------------------------------------------------------------------+
//| Establece el nivel absoluto de stop-loss                         |
//+------------------------------------------------------------------+
bool CPositionMT5::TakeProfitValue(double tp)
  {
   if(!PositionSelect(m_symbol))
      return false;
   return m_trade.Buy(0.0, m_symbol, 0.0, StopLossValue(), tp, NULL);
  }
//+------------------------------------------------------------------+
//| Cierra la posición actual según el mercado, estableciendo        |
//| un comentario de cierre, igual a comment                         |
//+------------------------------------------------------------------+
bool CPositionMT5::CloseAtMarket(string comment="")
  {
   if(!PositionSelect(m_symbol))
      return false;
   return m_trade.PositionClose(m_symbol);
  }
//+------------------------------------------------------------------+
//| Devuelve el volumen actual de la posición.                       |
//+------------------------------------------------------------------+
double CPositionMT5::Volume(void)
  {
   if(!PositionSelect(m_symbol))
      return false;
   return PositionGetDouble(POSITION_VOLUME);
  }
//+------------------------------------------------------------------+
//| Devuelve las ganancias actuales de la posición en la divisa      |
//| del depósito.                                                    |
//+------------------------------------------------------------------+
double CPositionMT5::Profit(void)
  {
   if(!PositionSelect(m_symbol))
      return false;
   return PositionGetDouble(POSITION_PROFIT);
  }

Como se puede ver, la principal tarea de la clase es retornar una u otra propiedad de la posición abierta actual. Además, la clase proporciona varios métodos para gestionar posición actual: el cierre de la misma, cambiar su nivel de Take Profit y Stop Loss.

 

Prototipo de estrategia comercial — la clase CStrategy

Hemos analizado varios módulos que realizan las tareas más extendidas. También se ha analizado el modelo de eventos, el modelo de toma de decisiones y el algoritmo de acciones de un asesor experto típico. Ha llegado el momento de combinar la información en un solo módulo de expertos comerciales, la clase CStrategy. Las tareas que se le asignan son muy diversas, aquí tenemos algunas de ellas:

  • La organización de una única secuencia de acciones comerciales para todas las estrategias, basadas en CStrategy.
  • La identificación de los eventos Timer, NewTick, BookEvent, NewBar y la transferencia de estos eventos a la estrategia de usuario.
  • La organización de un acceso sencillo a las cotizaciones del instrumento en base al modelo de MetaTrader 4.
  • La gestión de los modos de comercio.
  • El acceso sencillo a la información sobre las posiciones abiertas y la obtención de estadística sobre ella.

La propia clase CStrategy es bastante grande, y no vamos a mostrar el código fuente completo. En lugar de ello, nos centraremos en los algoritmos más importantes de esta clase. Lo primero que analizaremos es su modelo de eventos. Este modelo ya debe ser familiar para nosotros. Solo nos queda por analizar la organización específica para recibir eventos y su entrega a la estrategia específica. CStrategy es la clase progenitora de nuestra futura estrategia. En consecuencia, posee métodos bien conocidos para nosotros, que señalizan la llegada de los eventos correspondientes:

//+------------------------------------------------------------------+
//| Clase básica de la estrategia-capa.                              |
//+------------------------------------------------------------------+
class CStrategy : public CObject
  {
   //...
   void              OnTick(void);
   void              OnTimer(void);
   void              OnBookEvent(string symbol);
   virtual void      OnTradeTransaction(const MqlTradeTransaction &trans,
                                        const MqlTradeRequest &request,
                                        const MqlTradeResult &result);
   //...
  };

Vale la pena señalar que todos los métodos manejadores de eventos, excepto OnTradeTransaction, no son virtuales. Esto significa que, a nivel de la propia estrategia no se pueden procesar directamente los eventos Tick, Timer o BookEvent. En lugar de ello, CStrategy se ocupa del procesamiento de estos eventos por su cuenta. El evento TradeTransaction no se usa en él, por eso este método es virtual.

Ahora recurriremos al contenido de los métodos OnTick, OnTimer y OnBookEvent:

//+------------------------------------------------------------------+
//| Se llama al gestor de estregias, al darse el evento de           |
//| sistema 'nuevo tick'.                                            |
//+------------------------------------------------------------------+
void CStrategy::OnTick(void)
  {
   NewTickDetect();
   NewBarsDetect();
  }
//+------------------------------------------------------------------+
//| Se llama con el gestor de estrategias, al darse el  evento       |
//| de sistema 'OnTimer'.                                            |
//+------------------------------------------------------------------+
void CStrategy::OnTimer(void)
  {
   m_event.symbol=Symbol();
   m_event.type=MARKET_EVENT_TIMER;
   m_event.period=(ENUM_TIMEFRAMES)Period();
   CallSupport(m_event);
   CallInit(m_event);
   NewTickDetect();
   NewBarsDetect();
  }
//+------------------------------------------------------------------+
//| Se llama con el gestor de estrategias, al darse el  evento       |
//| de sistema 'OnBookEvent'.                                        |
//+------------------------------------------------------------------+
void CStrategy::OnBookEvent(string symbol)
  {
   m_event.symbol=symbol;
   m_event.type=MARKET_EVENT_BOOK_EVENT;
   m_event.period=PERIOD_CURRENT;
   CallSupport(m_event);
   CallInit(m_event);
   NewTickDetect();
   NewBarsDetect();
  }

Como se mencionó anteriormente, el evento de sistema NewTick sólo se aplica al instrumento en el que se está ejecutando el experto actual. Para convertir el evento NewTick en multiinstrumental, se debe utilizar un manejador especial de nuevos ticks y también realizar un seguimiento de la aparición de nuevas barras. Esta tarea se delega en los métodos privados correspondientes NewBarDetect y NewTickDetect. Veamos su código fuente:

//+------------------------------------------------------------------+
//| Determina la llegada de nuevas barras y genera el evento         |
//| correspondiente para el experto.                                 |
//+------------------------------------------------------------------+
void CStrategy::NewBarsDetect(void)
  {
   if(m_bars_detecors.Total()==0)
      AddBarOpenEvent(ExpertSymbol(),Timeframe());
   for(int i=0; i<m_bars_detecors.Total(); i++)
     {
      CBarDetector *bar=m_bars_detecors.At(i);
      if(bar.IsNewBar())
        {
         m_event.period = bar.Timeframe();
         m_event.symbol = bar.Symbol();
         m_event.type=MARKET_EVENT_BAR_OPEN;
         CallSupport(m_event);
         CallInit(m_event);
        }
     }
  }
//+------------------------------------------------------------------+
//| Detecta la aparición de nuevos ticks en los instrumentos         |
//| múltiples.                                                       |
//+------------------------------------------------------------------+
void CStrategy::NewTickDetect(void)
  {
   if(m_ticks_detectors.Total()==0)
      AddTickEvent(ExpertSymbol());
   for(int i=0; i<m_ticks_detectors.Total(); i++)
     {
      CTickDetector *tick=m_ticks_detectors.At(i);
      if(tick.IsNewTick())
        {
         m_event.period=PERIOD_CURRENT;
         m_event.type=MARKET_EVENT_TICK;
         m_event.symbol=tick.Symbol();
         CallSupport(m_event);
         CallInit(m_event);
        }
     }
  }

El funcionamiento de estos métodos consiste en la iteración de los detectores de nuevos ticks y barras (las clases CTickDetector y CBarDetector, respectivamente, hemos escrito sobre ellas en el apartado correspondiente). Cada uno de estos detectores se ajusta previamente al instrumento según el cual se lleva a cabo el comercio en realidad. Si se forma un nuevo tick o barra, se llama a los métodos especiales CallSupport y CallInit, que llaman de una forma especial a los métodos comerciales de la estrategia inmediata.

El algoritmo general de métodos-manejadores de eventos, incluyendo NewBarsDetect y NewTickDetect, es el siguiente:

  1. se detecta la llegada de un nuevo evento;
  2. se rellena la estructura de MarketEvent, indicando el identificador de evento correspondiente y sus atributos;
  3. se llama al método CallSupport, que a su vez llama a los métodos virtuales SupportBuy y SupportSell con el evento transmitido al método;
  4. de una manera análoga se llama al método CallInit, que llama a los métodos virtuales InitBuy y InitSell con el evento transmitido al método.

Sólo queda por analizar los métodos CallInit y CallSupport, para entender cómo la gestión se transfiere a la estrategia inmediata. Aquí está el código fuente de CallInit:

//+------------------------------------------------------------------+
//| Llama a la lógica de apertura de una posición, a condición       |
//| de que el estado comercial no prohiba claramente hacerlo.        |
//+------------------------------------------------------------------+
void CStrategy::CallInit(const MarketEvent &event)
  {
   m_trade_state=m_state.GetTradeState();
   if(m_trade_state == TRADE_STOP)return;
   if(m_trade_state == TRADE_WAIT)return;
   if(m_trade_state == TRADE_NO_NEW_ENTRY)return;
   SpyEnvironment();
   if(m_trade_state==TRADE_BUY_AND_SELL || m_trade_state==TRADE_BUY_ONLY)
      InitBuy(event);
   if(m_trade_state==TRADE_BUY_AND_SELL || m_trade_state==TRADE_SELL_ONLY)
      InitSell(event);
  }

El método recibe el estado comercial del módulo m_state (clase CTradeState, sobre la que escribimos en el artículo anterior) y decide si es posible con este estado comercial llamar a los métodos de inicialización de nuevas posiciones. Si el estado comercial prohíbe el comercio, entonces estos métodos no se invocan, de lo contrario, se les llama indicando el evento que sirvió de llamada del propio método CallInit.

De un modo similar funciona el método CallSupport, pero su lógica es algo diferente. Vamos a mostrar su código fuente:

//+------------------------------------------------------------------+
//| Llama a la lógica de seguimiento de una posición, a condición    |
//| de que el estado comercial no sea igual a TRADE_WAIT.            |
//+------------------------------------------------------------------+
void CStrategy::CallSupport(const MarketEvent &event)
  {
   m_trade_state=m_state.GetTradeState();
   if(m_trade_state == TRADE_WAIT)return;
   SpyEnvironment();
   for(int i=ActivePositions.Total()-1; i>=0; i--)
     {
      CPosition *pos=ActivePositions.At(i);
      if(pos.ExpertMagic()!=m_expert_magic)continue;
      if(pos.Direction()==POSITION_TYPE_BUY)
         SupportBuy(event,pos);
      else
         SupportSell(event,pos);
      if(m_trade_state==TRADE_STOP && pos.IsActive())
         ExitByStopRegim(pos);
     }
  }

El método obtiene el estado comercial actual del experto de una manera análoga y, si este le permite pasar a la gestión de posiciones, comienza la iteración de todas las posiciones abiertas en el momento actual. Estas posiciones son representadas por la clase CPosition. Tras obtener acceso a cada posición, el método compara su número mágico con el número mágico del experto actual y, si coinciden, llama a los métodos correspondientes de acompañamiento: SupportBuy para la posición larga y SupportSell para la corta.

 

Conclusión

Hemos examinado todos los módulos principales que entran en la clase CStrategy, con cuya ayuda ofrece la amplia funcionalidad de la estrategia final del usuario. La estrategia heredada de esta clase adquiere capacidad para trabajar con las cotizaciones de precio y los eventos comerciales. Además, la estrategia de usuario adopta la secuencia unificada de acciones comerciales que le impone la clase CStrategy. La propia estrategia de usuario puede ser escrita como un módulo independiente, que describe por sí mismo solamente las operaciones comerciales y las reglas por las que se realizan. Una "división del trabajo" semejante ayudará a reducir significativamente el gasto de recursos de todo tipo a la hora de desarrollar un algoritmo.

El próximo artículo "Experto comercial universal: Las estrategias de usuario y las clases comerciales auxiliares (Parte 3)" va a tratar sobre la construcción inmediata de estrategias comerciales sobre la base de los algoritmos descritos.

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

Archivos adjuntos |
strategyarticle.zip (100.23 KB)
Interfaces gráficas I: Preparación de la estructura de la biblioteca (Capítulo 1) Interfaces gráficas I: Preparación de la estructura de la biblioteca (Capítulo 1)

Con este artículo yo empiezo otra serie más que concierne al desarrollo de las interfaces gráficas. Actualmente, no hay ninguna librería del código que permita crear fácil y rápidamente las interfaces gráficas en las aplicaciones MQL. Me refiero a las interfaces gráficas a las que estamos acostumbrados en los sistemas operativos comunes.

Experto comercial universal: Los modos comerciales de las estrategias (Parte 1) Experto comercial universal: Los modos comerciales de las estrategias (Parte 1)

Cada escritor de expertos, independientemente de su nivel de preparación, se encuentra todos los días con las mismas tareas comerciales y problemas algorítmicos, que debe resolver de una forma u otra para organizar un proceso comercial fiable. Este artículo describe las capacidades del motor comercial CStrategy, capaz de ocuparse de la resolución de estas tareas y de proporcionar al usuario mecanismos cómodos para describir sus ideas sobre trading.

Interfaces gráficas I: Formulario para controles (Capítulo 2) Interfaces gráficas I: Formulario para controles (Capítulo 2)

En este artículo vamos a crear el primero y el más importante elemento de las interfaces gráficas: formulario para los controles. A este formulario se le puede adjuntar múltiples controles de diferentes tipos en cualquier orden y secuencia.

Interfaces gráficas I: "Animar" la interfaz gráfica (Capítulo 3) Interfaces gráficas I: "Animar" la interfaz gráfica (Capítulo 3)

En el artículo anterior de esta serie hemos empezado a desarrollar la clase del formulario para los controles. En este artículo continuaremos el desarrollo de la clase llenándola con los métodos para el desplazamiento del formulario dentro del área del gráfico, así como integraremos este elemento de la interfaz en el núcleo de la librería. Además de eso, configuraremos todo de tal manera que, al situar el cursor sobre los controles del formulario, éstos cambien su color.