Experto comercial universal: integración con los módulos estándar de señales de MetaTrader (parte 7)

Vasiliy Sokolov | 22 julio, 2016


Capítulos


Introducción

En las partes anteriores de este artículo hemos analizado los mecanismos que posibilitan la creación sencilla y eficaz de algoritmos comerciales, para ser más concretos, hemos creado CStartegy. El proyecto lleva desarrollándose de forma ininterrumpida más de medio año. Durante este tiempo, CStrategy se ha ido armando con muchos módulos que hacen el proceso comercial más efectivo y seguro desde el punto de vista de la ejecución técnica de las operaciones comerciales. Sin embargo, le seguía haciendo falta una capacidad muy importante. A pesar de que CStrategy es una aplicación estrictamente orientada a objetos, ha seguido permaneciendo como una "cosa en sí misma". El enfoque orientado a objetos postula que el código debe tener un carácter modular y abierto. En esencia, la base de códigos, dentro de la mejor tradición del enfoque, deberá fundarse en clases generales extendidas. Esto toca en especial al modelo comercial y a los modelos de formación de señales. Si bien la lógica comercial de CStrategy se basa a cierto nivel en el módulo CStrategy estándar, es cierto que hasta el día de hoy las cosas no han ido como desearíamos con la base de señales de CStrategy. En otras palabras, CStrategy no contiene ningún módulo responsable de la generación de señales comerciales. Hasta el momento actual, cualquier usuario que quisiera utilizar CStrategy debía reescribir la lógica del experto "desde cero", mientras que en el suministro estándar de MetaTrader 5 ya podía disponer de la señal necesaria preparada. Para que esto no suceda, se tomó la decisión de completar CStrategy con mecanismos para trabajar con la base de señales estándar de MetaTrader 5. En esta artículo explico de qué forma se puede integrar CStrategy con uno de los módulos de señales estándar y mostraré cómo crear una estrategia propia, recurriendo exclusivamente a algoritmos ya preparados.

 

Breve panorámica de las clases usadas por el autogenerador de estrategias

Para generar estrategias comerciales con la ayuda del wizard MQL se usa el conjunto de clases diferentes incluido en el suministro estándar de MetaTrader. Este se encuentra en forma de archivos mqh en los subcatálogos correspondientes del directorio MQL5\Include. De forma convencional, estas clases (o módulos) se pueden dividir en varias categorías. Vamos a enumerarlas.

  • Clases básicas de organización de datos (CObject, CArrayDouble, CArrayObj y otras). Sobre su base se construyen todos los demás módulos comerciales.
  • Clases para organizar el acceso a los búferes de los indicadores (CDoubleBuffer, CIndicatorBuffer). Por consiguiente, se usan para trabajar con los indicadores.
  • Clases-indicadores y clases-series temporales basadas en la clase CSeries.
  • La clase básica del experto CBaseExpert y la clase CExpert basada en él. En CBaseExpert se basan todos los módulos auxiliares, por ejemplo, el módulo de cálculo de capital y el módulo de gestión de trailing-stop. CExpert es la base de todos los expertos de usuario finales.
  • El módulo de señales sobre la base de CExpertSignal, fundamentado a su vez en el experto CEpertBase. Los módulos de señales generan señales comerciales de compra y venta. Para funcionar, usan las clases de los indicadores en los que se basan.
  • El módulo comercial CExpertTrade. Se basa en la clase CTrade y proporciona acceso a la realización de operaciones comerciales.

En el diagrama de más abajo se muestra el esquema general de herencia vertical de las clases implicadas durante la generación automática de estrategias:

 

Fig. 1. Esquema de herencia de las clases estándar del autogenerador de estrategias

En la figura se representan solo las clases básicas y algunas derivadas. No se muestran todos los indicadores heredados de CIndicators. Tampoco se representa en este esquema ningún módulo de trailing o gestión de capital, ni los módulos de señales. En lugar de ello, se marcan solo las interacciones elementales. De toda la jerarquía de clases nos interesa solamente un grupo: las clases de las señales de CExpertSignal y sus clases derivadas. En la figura 1 este grupo se destaca con una línea punteada de color verde.

Aparte de las relaciones verticales, las clases forman sistemas de conexión (relaciones horizontales). Así, por ejemplo, los módulos de señales usan de forma activa las clases de los indicadores que a su vez implican los búferes de indicador. Las diferentes multitudes son partes unas de otras. Por ejemplo, los módulos de gestión de capital son simultáneamente asesores expertos (por lo menos, al nivel de CExpertBase), aunque la respuesta a la pregunta de si pueden estos módulos tener algo en común con el experto no resulte obvia.

Para crear estas clases, normalmente, son necesarias complejas cadenas de inicialización. Así, al crear un objeto de una señal, por ejemplo CSignalMacd, en general es necesario ejecutar la inicialización de la propia señal, inicializar los indicadores correspondientes en los que se basa, y también inicializar las series temporales necesarias (herederas de la clase CPriceSeries), imprescindibles para que la señal pueda funcionar. Puesto que el objeto también se inicializa con objetos complejos, estos deben ser igualmente inicilizados (igual que en el caso con las series temporales). De esta forma, la cuestión de la inicialización es una de las más complejas en el uso de la biblioteca descrita.

Veamos un ejemplo concreto. Supongamos que necesitamos inicializar el módulo CMacdSignal dentro de su propia clase al crear el objeto correspondiente de esta clase. Entonces, el código de inicialización del módulo de la señal será el siguiente:

//+------------------------------------------------------------------+
//| Inicialización del módulo de señales CSignalMacd                        |
//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(void)
{
   CSymbolInfo* info = new CSymbolInfo();                // Creación del objeto que representa el instrumento comercial de la estrategia
   info.Name(Symbol());                                  // Inicialización del objeto que representa el instrumento comercial de la estrategia
   m_signal_ma.Init(info, Period(), 10);                 // Inicialización del módulo de señales con el instrumento comercial y el marco temporal
   m_signal_ma.InitIndicators(GetPointer(m_indicators)); // Creación en el módulo de señales de los indicadores necesarios basándose en la lista vacía de indicadores m_indicators
   m_signal_ma.EveryTick(true);                          // Modo de simulación
   m_signal_ma.Magic(ExpertMagic());                     // Número mágico
   m_signal_ma.PatternsUsage(8);                         // Máscara del patrón
   m_open.Create(Symbol(), Period());                    // Inicialización de la serie temporal de los precios de apertura
   m_high.Create(Symbol(), Period());                    // Inicialización de la serie temporal de los precios máximos
   m_low.Create(Symbol(), Period());                     // Inicialización de la serie temporal de los precios mínimos
   m_close.Create(Symbol(), Period());                   // Inicialización de la serie temporal de los precios de cierre
   m_signal_ma.SetPriceSeries(GetPointer(m_open),        // Inicialización del módulo de señales con objetos-series temporales
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
}

Merece la pena notar que los problemas de inicialización no son actuales para los usuarios que usen el autogenerador de estrategias. Toda la cadena de inicializaciones se crea de forma automática en el generador de estrategias, y todo lo que necesita el usuario es simplemente comenzar a usar el experto. La cuestión será distinta para aquellos que quieran usar este conjunto de clases para crear sus propias soluciones. En este caso, será necesario ejecutar toda la cadena de inicialización independientemente.

 

Breve panorámica de los módulos de señales, el concepto de patrón

Como ya hemos dicho, los módulos de señales se basan en la clase general CExpertSignal, que a su vez se basa en CExpertBase. Esencialmente, cada módulo de señales estándar es una clase con funciones de búsqueda de uno o varios patrones: condiciones lógicas especiales que permiten determinar el momento de compra o venta. Por ejemplo, si la media móvil rápida cruza la lenta de abajo hacia a arriba, entonces forma un cierto patrón de compra. Cada indicador puede formar varias condiciones de compra y venta. Un ejemplo sencillo es el indicador MACD. Como señal de entrada en el mercado puede servir tanto la divergencia de este indicador, como el cruce sencillo de la línea de señal con el histograma del indicador. Ambas condiciones comerciales son patrones que se diferencian el uno del otro. Al darse uno o varios de estos eventos, es posible la entrada en el mercado. Es importante destacar que los patrones de compra y venta se diferencian, y normalmente son opuestos el uno con respecto al otro. Desde el punto de vista del autogenerador de estrategias, la señal constituye un cierto procesador de uno o varios patrones conectados por un solo indicador. Por ejemplo, una señal basada en el indicador MACD es capaz de determinar la presencia en el mercado de varios patrones a la vez. Un módulo de señales estándar concreto basado en el indicador MACD contiene cinco patrones de venta y cinco de compra:

  • Señal "Viraje", el oscilador ha virado hacia arriba (para la compra) o hacia abajo (para la venta)
  • Cruce de la línea principal y la línea de señal
  • Crece del nivel cero
  • Divergencia
  • Divergencia doble

Semejante definición de los patrones se describe en la guía de ayuda estándar del terminal, por eso no vamos a centrar la atención del lector en ello. Los otros módulos de señales contiene otros patrones y su cantidad puede diferenciarse. Normalmente, cada señal contiene de media tres patrones de compra y tres patrones de venta. Como máximo, cada señal puede contener 32 patrones en cada lado (precisamente esta es la longitud del campo de bits de la variable entera integer, que guarda la máscara de los patrones utilizados).

La señal es capaz no solo de determinar los patrones, sino de dar ciertas recomendaciones en forma de número entero. Se supone que este número debe mostrar la fuerza de la señal: cuanto mayor sea el número, más fuerte será la señal. Merece la pena destacar que se puede definir la fuerza de este u otro patrón a través de un grupo especial de métodos en forma de Pattern_x(int value), donde x es el número del patrón. Por ejemplo, en el código del configurador de la señal veremos escrito lo siguinete:

//+------------------------------------------------------------------+
//| Inicialización del módulo de señales CSignalMacd                        |
//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(void)
{
   m_signal_ma.Pattern_0(0);
   m_signal_ma.Pattern_1(0);
   m_signal_ma.Pattern_2(0);
   m_signal_ma.Pattern_3(100);
   m_signal_ma.Pattern_4(0);
   m_signal_ma.Pattern_5(0);
}

En este caso, el módulo de señales CSignalMacd retornará el valor solo cuando se forme la divergencia: el patrón del indicador MACD cuando el primer valle analizado del oscilador sea más superficial que el anterior, y el valle de precio que le corresponda sea más profundo que el anterior (la definición es para la compra). Todos estos patrones serán omitidos.

Las recomendaciones comerciales se retornan con los dos métodos independientes LongCondition y ShortCondition. El primero de ellos retorna las recomendaciones para la compra, el segundo, consecuentemente, para la venta. Para comprender cómo funcionan, echaremos un vistazo a uno de ellos, LongCondition:

//+------------------------------------------------------------------+
//| Indicación de que el precio va a crecer.                            |
//+------------------------------------------------------------------+
int CSignalMACD::LongCondition(void)
  {
   int result=0;
   int idx   =StartIndex();
//--- comprobación de la dirección de la línea principal
   double diff = DiffMain(idx);
   if(diff>0.0)
     {
      //--- la línea principal está dirigida hacia arriba, lo que confirma la posibilidad del crecimiento del precio
      if(IS_PATTERN_USAGE(0))
         result=m_pattern_0;      // confirmación de la señal con el número 0
      //--- si se usa el modelo 1, se buscará el viraje de la línea principal
      if(IS_PATTERN_USAGE(1) && DiffMain(idx+1)<0.0)
         result=m_pattern_1;      // señal número 1
      //--- si se usa el modelo 2, se buscará el cruce de la línea principal y la línea de señal
      if(IS_PATTERN_USAGE(2) && State(idx)>0.0 && State(idx+1)<0.0)
         result=m_pattern_2;      // señal número 2
      //--- si se usa el modelo 3, se buscará el cruce de la línea principal con el nivel cero 
      if(IS_PATTERN_USAGE(3) && Main(idx)>0.0 && Main(idx+1)<0.0)
         result=m_pattern_3;      // señal número 3
      //--- si se usan los modelos 4 o 5, y la línea principal está por debajo del nivel y está aumentando, se buscará la divergencia 
      if((IS_PATTERN_USAGE(4) || IS_PATTERN_USAGE(5)) && Main(idx)<0.0)
        {
         //--- se ejecuta el análisis ampliado del estado del oscilador 
         ExtState(idx);
         //--- si se usa el modelo 4, se espera la señal "divergencia" 
         if(IS_PATTERN_USAGE(4) && CompareMaps(1,1)) // 0000 0001b
            result=m_pattern_4;   // señal número 4
         //--- si se usa el modelo 5, se espera la señal "divergencia doble" 
         if(IS_PATTERN_USAGE(5) && CompareMaps(0x11,2)) // 0001 0001b
            return(m_pattern_5);  //señal número 5
        }
     }
//--- se retorna el resultado
   return(result);
  }

 El método trabaja con el macros de definición del patrón IS_PATTERN_USAGE, basado en una máscara de bits:

//--- comprobación del uso del modelo de mercado
#define IS_PATTERN_USAGE(p)          ((m_patterns_usage&(((int)1)<<p))!=0)

Si el patrón se usa y las condiciones que le corresponden han sido ejecutadas, el resultado de la recomendación será igual al peso correspondiente del patrón que, a su vez, es definido por el usuario a través de los métodos del grupo Pattern_x. Además, si se usan varios patrones, no será posible saber cuál de ellos funcione y cuál no, puesto que la comprobación no contiene operadores break. La fuerza de la recomendación en este caso será igual a la fuerza del patrón que haya sido definido en el último lugar.

El número del patrón que se debe usar se establece con una máscara de bits especial. Por ejemplo, si es necesario usar el patrón №3, será imprescindible que el cuarto bit de la variable de 32 bits sea igual a la unidad (recordemos que la indexación de patrones comienza partiendo de cero, por eso para el tercer patrón se usa el cuarto dígito, y para el cero, el primer dígito del campo). Si convertimos la cifra 1000 del sistema binario al decimal, obtenemos la cifra 8. Precisamente esta cifra se debe transmitir al método PatternsUsage. Es posible combinar los patrones. Por ejemplo, para usar el patrón № 3 junto con el patrón № 2, es necesario componer un campo de bits cuyos dígitos cuarto y tercero sean igual a la unidad: 1100. Este mismo valor en formato decimal será igual a 12.

 

El módulo de señales. Primer uso

Ahora sabemos lo suficiente para comenzar a usar los módulos de señales.  Realizaremos nuestros experimentos sobre la base de CStrategy, para lo cual crearemos una clase especial experimental, la estrategia CSignalSamples:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/es/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/es/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Expert\Signal\SignalMACD.mqh>
//+------------------------------------------------------------------+
//| La estrategia recibe los eventos y los muestra en el terminal.             |
//+------------------------------------------------------------------+
class CSignalSamples : public CStrategy
{
private:
   CSignalMACD       m_signal_macd;
   CSymbolInfo       m_info;
   CiOpen            m_open;
   CiHigh            m_high;
   CiLow             m_low;
   CiClose           m_close;
   CIndicators       m_indicators;
public:
                     CSignalSamples(void);
   virtual void      OnEvent(const MarketEvent& event);                     
};
//+------------------------------------------------------------------+
//| Inicialización del módulo de señales CSignalMacd                        |
//+------------------------------------------------------------------+
CSignalSamples::CSignalSamples(void)
{
   m_signal_macd.Pattern_0(0);
   m_signal_macd.Pattern_1(0);
   m_signal_macd.Pattern_2(0);
   m_signal_macd.Pattern_3(100);
   m_signal_macd.Pattern_4(0);
   m_signal_macd.Pattern_5(0);
   m_info.Name(Symbol());                                  // Inicialización del objeto que representa el instrumento comercial de la estrategia
   m_signal_macd.Init(GetPointer(m_info), Period(), 10);   // Inicialización del módulo de señales con el instrumento comercial y el marco temporal
   m_signal_macd.InitIndicators(GetPointer(m_indicators)); // Creación en el módulo de señales de los indicadores necesarios basándose en la lista vacía de indicadores m_indicators m_indicators
   m_signal_macd.EveryTick(true);                          // Modo de simulación
   m_signal_macd.Magic(ExpertMagic());                     // Número mágico
   m_signal_macd.PatternsUsage(8);                         // Máscara del patrón
   m_open.Create(Symbol(), Period());                      // Inicialización de la serie temporal de los precios de apertura
   m_high.Create(Symbol(), Period());                      // Inicialización de la serie temporal de los precios máximos
   m_low.Create(Symbol(), Period());                       // Inicialización de la serie temporal de los precios mínimos
   m_close.Create(Symbol(), Period());                     // Inicialización de la serie temporal de los precios de cierre
   m_signal_macd.SetPriceSeries(GetPointer(m_open),        // Inicialización del módulo de señales con objetos-series temporales
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
                              
}
//+------------------------------------------------------------------+
//| Compras.                                                         |
//+------------------------------------------------------------------+
void CSignalSamples::OnEvent(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_sell = m_signal_macd.ShortCondition();
   int power_buy = m_signal_macd.LongCondition();
   if(power_buy != 0 || power_sell != 0)
      printf("PowerSell: " + (string)power_sell + " PowerBuy: " + (string)power_buy);
}
//+------------------------------------------------------------------+

En las últimas versiones de CStrategy se ha añadido el evento de amplia difusión OnEvent, representado con el método homónimo OnEvent, que se llama cada vez que llega algún evento. A diferencia de los métodos BuyInit, SellInit, BuySupport y SellSupport, que conocemos mejor, OnEvent se llama independientemente del modo de la estrategia comercial y del horario comercial. De esta forma, OnEvent proporciona acceso al flujo de eventos de la estrategia, dando apoyo, además, a un estricto modelo de eventos. OnEvent es muy cómodo de usar para cálculos o acciones generales no relacionados con direcciones concretas de compra o venta.

La mayor parte del código la ocupa la inicialización del módulo de señales comerciales. Como ejemplo, se ha usado un módulo basado en el indicador MACD: CSignalMACD. En el módulo solo se ha usado un patrón, el número 3. Se le ha designado un peso de 100, así como el método correspondiente PatternsUsage con un valor de 8. Para iniciar esta estrategia es necesario crear el cargador de estrategias correspondiente o un módulo ejecutable mq5. Vamos a mostrar su contenido:

//+------------------------------------------------------------------+
//|                                                       Agent.mq5  |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/es/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/es/users/c-4"
#property version   "1.00"
#include <Strategy\StrategiesList.mqh>
#include <Strategy\Samples\SignalSamples.mqh>
CStrategyList Manager;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() 
{
   CSignalSamples* signal = new CSignalSamples();
   signal.ExpertMagic(2918);
   signal.ExpertName("MQL Signal Samples");
   signal.Timeframe(Period());
   if(!Manager.AddStrategy(signal))
      delete signal;
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
  Manager.OnTick();
}
//+------------------------------------------------------------------+
//| OnChartEvent function                                               |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
   Manager.OnChartEvent(id, lparam, dparam, sparam);
}

Este consta de la inicialización de la estrategia en la función OnInit y del procesador de nuevos ticks OnTick. Después de ejecutar el código resultante en el simulador de estrategias en el modo de visualización, se representarán los mensajes correspondientes sobre las señales obtenidas:

2016.06.20 16:34:31.697 tester agent shutdown finished
2016.06.20 16:34:31.642 shutdown tester machine
2016.06.20 16:34:31.599 tester agent shutdown started
2016.06.20 16:34:31.381 log file "Z:\MetaTrader 5\Tester\Agent-127.0.0.1-3000\logs\20160620.log" written
2016.06.20 16:34:31.381 325 Mb memory used including 28 Mb of history data, 64 Mb of tick data
2016.06.20 16:34:31.381 EURUSD,M1: 51350 ticks (12935 bars) generated in 0:00:00.780 (total bars in history 476937, total time 0:00:00.843)
2016.06.20 16:34:31.376 final balance 100000.00 USD
2016.06.20 16:34:31.373 2016.04.14 22:12:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 22:01:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 21:24:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:54:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 20:50:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:18:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 20:14:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:13:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:07:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.372 2016.04.14 19:48:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.372 2016.04.14 18:48:00   PowerSell: 0 PowerBuy: 100
...
...

Este mensaje nos indica que las señales solicitadas se han recibido con éxito, y que podemos seguir integrando el módulo de señales en nuestra estrategia.

 

Descripción de la primera estrategia basada en CSignalMACD

Ha llegado el momento de escribir una estrategia completa basada en el módulo de señales CSignalMACD. El primer patrón que usaremos será el patrón de cruce de la línea de señal con el oscilador del indicador. Usamos la documentación. En la guía de usuario, abrimos el Manual de referencia MQL5 -> Biblioteca estándar -> Clases básicas de las estrategias -> Módulos de señales comerciales -> Señales del oscilador MACD. Buscamos el segundo patrón — "Cruce de la línea principal y de la línea de señal". Mostraremos su descripción.

  • Compra: "Cruce de la línea principal y la línea de señal", en la barra analizada la línea principal está por encima de la línea de señal, y en la anterior, por debajo.

  • Fig 2. El oscilador cruza la línea de señal de abajo hacia arriba

  • Venta: "Cruce de la línea principal y la línea de señal", en la barra analizada la línea principal está por debajo de la línea de señal, y en la anterior, por encima.

Fig. 3. El oscilador cruza la línea de señal de arriba hacia abajo

Debemos determinar el número del patrón que corresponde a esta descripción. En esta tarea nos ayudará el encabezamiento de la clase CSignalMACD:

//+------------------------------------------------------------------+
//| Class CSignalMACD.                                               |
//| Purpose: Class of generator of trade signals based on            |
//|          the 'Moving Average Convergence/Divergence' oscillator. |
//| Is derived from the CExpertSignal class.                         |
//+------------------------------------------------------------------+
class CSignalMACD : public CExpertSignal
  {
protected:
   CiMACD            m_MACD;           // objeto-oscilador
   //--- adjusted parameters
   int               m_period_fast;    // parámetro del oscilador "periodo de la EMA rápida" 
   int               m_period_slow;    // parámetro del oscilador "periodo de la EMA lenta" 
   int               m_period_signal;  // parámetro del oscilador "periodo de promediación de la diferencia" 
   ENUM_APPLIED_PRICE m_applied;       // parámetro del oscilador "series de precios" 
   //--- "weights" of market models (0-100)
   int               m_pattern_0;      // modelo 0 "el oscilador se mueve en la dirección necesaria"
   int               m_pattern_1;      // modelo 1 "viraje del oscilador hacia la dirección necesaria"
   int               m_pattern_2;      // modelo 2 "Cruce de la línea principal y de la línea de señal"
   int               m_pattern_3;      // modelo 3 "Cruce de la línea principal y el nivel cero"
   int               m_pattern_4;      // modelo 4 "divergencia del oscilador y el precio"
   int               m_pattern_5;      // modelo 5 "divergencia doble del oscilador y el precio"
   //--- variables
   double            m_extr_osc[10];   // matriz de los valores de los extremos del oscilador 
   double            m_extr_pr[10];    // matriz de los valores de los extremos correspondientes del precio 
   int               m_extr_pos[10];   // matriz de los desplazamientos de los extremos (en barras) 
   uint              m_extr_map;       // mapa de bits resultante de los extremos del oscilador y el precio 
...
  }

Gracias a los comentarios dejados en el código con toda previsión, se puede ver que este tipo de patrón es el número dos.

Ahora que ya sabemos el número del patrón, necesitamos configurar la señal de la forma que corresponde. En primer lugar, para evitar confusiones, no vamos a usar otros patrones. Para ello, estableceremos una máscara de patrones igual a 4 (100 en código binario). Ya que solo vamos a usar un patrón, conocer la fuerza de la señal (lo que conlleva también configurar la fuerza de los patrones) no será algo necesario, puesto que o bien existe señal, o bien no, no hay una tercera opción. Debido a que comprobaremos la señal solo en la apertura de una nueva barra, será necesario indicarlo llamando al método correspondiente de la señal EveryTick con la bandera false. Esta configuración la implementaremos en el constructor de estrategias. A continuación, pasaremos a la lógica comercial en cuestión. Para este cometido, redefiniremos los métodos InitBuy, SupportBuy, InitSell, SupportSell. La propia estrategia la llamaremos COnSignalMACD: el prefijo On indica que la estrategia se basa en un módulo de señales estándar. El código de la estrategia se muestra más abajo:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/es/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/es/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Expert\Signal\SignalMACD.mqh>
//+------------------------------------------------------------------+
//| La estrategia recibe los eventos y los muestra en el terminal.             |
//+------------------------------------------------------------------+
class COnSignalMACD : public CStrategy
{
private:
   CSignalMACD       m_signal_macd;
   CSymbolInfo       m_info;
   CiOpen            m_open;
   CiHigh            m_high;
   CiLow             m_low;
   CiClose           m_close;
   CIndicators       m_indicators;
public:
                     COnSignalMACD(void);
   virtual void      InitBuy(const MarketEvent &event);
   virtual void      InitSell(const MarketEvent &event);
   virtual void      SupportBuy(const MarketEvent& event, CPosition* pos);
   virtual void      SupportSell(const MarketEvent& event, CPosition* pos);
};
//+------------------------------------------------------------------+
//| Inicialización del módulo de señales CSignalMacd                        |
//+------------------------------------------------------------------+
COnSignalMACD::COnSignalMACD(void)
{
   m_info.Name(Symbol());                                  // Inicialización del objeto que representa el instrumento comercial de la estrategia
   m_signal_macd.Init(GetPointer(m_info), Period(), 10);   // Inicialización del módulo de señales con el instrumento comercial y el marco temporal
   m_signal_macd.InitIndicators(GetPointer(m_indicators)); // Creación en el módulo de señales de los indicadores necesarios basándose en la lista vacía de indicadores m_indicators m_indicators
   m_signal_macd.EveryTick(false);                         // Modo de simulación
   m_signal_macd.Magic(ExpertMagic());                     // Número mágico
   m_signal_macd.PatternsUsage(4);                         // Máscara del patrón
   m_open.Create(Symbol(), Period());                      // Inicialización de la serie temporal de los precios de apertura
   m_high.Create(Symbol(), Period());                      // Inicialización de la serie temporal de los precios máximos
   m_low.Create(Symbol(), Period());                       // Inicialización de la serie temporal de los precios mínimos
   m_close.Create(Symbol(), Period());                     // Inicialización de la serie temporal de los precios de cierre
   m_signal_macd.SetPriceSeries(GetPointer(m_open),        // Inicialización del módulo de señales con objetos-series temporales
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
}
//+------------------------------------------------------------------+
//| Compras.                                                         |
//+------------------------------------------------------------------+
void COnSignalMACD::InitBuy(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_buy = m_signal_macd.LongCondition();
   if(power_buy != 0)
      Trade.Buy(1.0);
}
//+------------------------------------------------------------------+
//| Cierre de compras                                                 |
//+------------------------------------------------------------------+
void COnSignalMACD::SupportBuy(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_sell = m_signal_macd.ShortCondition();
   //printf("Power sell: " + (string)power_sell);
   if(power_sell != 0)
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Ventas.                                                         |
//+------------------------------------------------------------------+
void COnSignalMACD::InitSell(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_sell = m_signal_macd.ShortCondition();
   if(power_sell != 0)
      Trade.Sell(1.0);
}
//+------------------------------------------------------------------+
//| Cierre de compras                                                 |
//+------------------------------------------------------------------+
void COnSignalMACD::SupportSell(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_buy = m_signal_macd.LongCondition();
   if(power_buy != 0)
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+

La apertura de posiciones largas y cortas sucede según las señales descritas en la guía de usuario. El cierre de las posiciones existentes anteriormente tiene lugar conforme a las señales opuestas. De esta forma, si llega el momento de apertura de una posición larga, la posición corta abierta con anterioridad será cerrada, y al contrario.

El resultado del comercio se puede ver en el simulador de estrategias. Presentamos en la figura de más abajo un fragmento de la historia de la simulación:

 

Fig. 4. Apertura de operaciones al cruzarse el histograma MACD y su línea de señal

De acuerdo con el modo de simulación, las operaciones se abren según la señal recibida en la barra anterior. En la figura se ve que en la barra siguiente tras el cruce del histograma MACD con su línea de señal tiene lugar la apertura de una posición larga o corta con el cierre simultáneo de la posición anterior.

 

El adaptador de señales

Ya hemos aclarado que antes de empezar a trabajar con una señal es necesario configurarla. La configuración de la señal tiene lugar con la ayuda de objetos complejos, que a su vez también deben ser configurados antes de transmitirlos a la propia señal. Para señales diferentes serán necesarios objetos diferentes. Por ejemplo, para unas será suficiente con indicar las series temporales, mientras que para otras habrá que indicar el contenedor de indicadores y los datos adicionales de precios, como el volumen de ticks o el volumen real. Todo esto complica el uso de las señales a nivel de usuario, puesto que exige del mismo conocimientos del dispositivo interno, así como información de los datos imprescindibles para su correcto funcionamiento.

Para evitar estas dificultades, se ha introducido una clase-adaptador especial, que simplifica el trabajo con las señales. Esta clase se llama CSignalAdapter, y se ubica en el directorio general del proyecto CStrategy. El adaptador posee una interfaz sencilla. Permite crear una señal y obtener las banderas de aparición de patrones de compra y venta. Para crear una señal es necesario transmitir los parámetros de esta señal al método especial CSignalAdapter::CreateSignal. Los propios parámetros de la señal se encuentran en la estructura especial MqlSignalParams. Vamos a mostrar la definición de esta estructura:

//+--------------------------------------------------------------------+
//| Signal parameters                                                  |
//+--------------------------------------------------------------------+
struct MqlSignalParams
{
public:
   string            symbol;           // Símbolo
   ENUM_TIMEFRAMES   period;           // Periodo del gráfico
   ENUM_SIGNAL_TYPE  signal_type;      // Tipo de la Señal
   int               index_pattern;    // Índice del patrón
   int               magic;            // Número mágico del experto
   double            point;            // Número de puntos
   bool              every_tick;       // Modo de simulación "Cada tick"
   void operator=(MqlSignalParams& params);
};
//+--------------------------------------------------------------------+
//| Se usa el operador de copiado, puesto que la estructura usa líneas|
//+--------------------------------------------------------------------+
void MqlSignalParams::operator=(MqlSignalParams& params)
{
   symbol = params.symbol;
   period = params.period;
   signal_type = params.signal_type;
   usage_pattern = params.usage_pattern;
   magic = params.magic;
   point = params.point;
   every_tick = params.every_tick;
}

La estructura contiene tipos básicos que definen las siguientes características de la señal:

  • símbolo del instrumento;
  • marco temporal del instrumento;
  • tipo de la señal;
  • número mágico del experto;
  • bandera que indica el modo de simulación y comercio "cada tick";
  • filtro de precio;
  • patrón de señal utilizado.

Hay que decir unas palabras aparte sobre el parámetro index_pattern. A diferencia del módulo de señales, este adopta, no una máscara de patrones, sino solo el índice de una de ellas. De esta forma, cada adaptador de señales puede usar solo un patrón de la señal indicada. El valor index_pattern debe encontrarse entre 1 y 31, y también corresponderse con el número real de patrones de la señal utilizada.

Aparte de los parámetros básicos, la estructura contiene un operador de copiado, puesto que usa en sus componentes un tipo de línea que no permite copiar de forma automática una estructura en otra. Después de que el usuario haya decidido los parámetros necesarios y haya rellenado con ellos la estructura correspondiente, podrá llamar al método CSignalAdapter::CreateSignal y obtener como respuesta de este método un ejemplar de la señal creada. El ejemplar obtenido se puede seguir configurando, teniendo en cuenta las peculiaridades específicas de la señal correspondiente.

En el listado de más abajo se muestra el método de configuración de la señal CSignalMACD con la ayuda del adaptador CSignalAdapter:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/es/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/es/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Strategy\SignalAdapter.mqh>

//+------------------------------------------------------------------+
//| La estrategia recibe los eventos y los muestra en el terminal.             |
//+------------------------------------------------------------------+
class CAdapterMACD : public CStrategy
{
private:
   CSignalAdapter    m_signal;
   MqlSignalParams   m_params;
public:
                     CAdapterMACD(void);
   virtual void      InitBuy(const MarketEvent &event);
   virtual void      InitSell(const MarketEvent &event);
   virtual void      SupportBuy(const MarketEvent& event, CPosition* pos);
   virtual void      SupportSell(const MarketEvent& event, CPosition* pos);
};
//+------------------------------------------------------------------+
//| Configuramos el adaptador                                             |
//+------------------------------------------------------------------+
CAdapterMACD::CAdapterMACD(void)
{
   m_params.symbol = Symbol();
   m_params.period = Period();
   m_params.every_tick = false;
   m_params.signal_type = SIGNAL_MACD;
   m_params.magic = 1234;
   m_params.point = 1.0;
   m_params.usage_pattern = 2;
   CSignalMACD* macd = m_signal.CreateSignal(m_params);
   macd.PeriodFast(15);
   macd.PeriodSlow(32);
   macd.PeriodSignal(6);
}

El adaptador exige que se configuren los parámetros exactamente de la misma forma. Sim embargo, a diferencia de la primera versión del experto, todos los parámetros son triviales, es decir, tipos básicos. Aparte, no será necesario crear y realizar un seguimiento de otros objetos complejos para el funcionamiento de la señal, como las series temporales o indicadores. Todo esto lo hace el ropio adaptador. Precisamente por ello su uso simplifica significativamente el trabajo con las señales.  

Preste atención a que hemos seguido configurando la señal después de que haya sido creada, estableciendo nuestros propios periodos del indicador MACD (15, 32, 6). Ha sido algo fácil de hacer, ya que el método CreateSignal nos ha retornado el objeto correspondiente.

Después de que la señal haya sido configurada y ajustada, podemos empezar a usarla. Con este fin, tenemos los métodos simples BuySignal y ShortSignal. Vamos a mostrar la continuación de nuestra clase de estrategia:

//+------------------------------------------------------------------+
//| Compras.                                                         |
//+------------------------------------------------------------------+
void CAdapterMACD::InitBuy(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.LongSignal())
      Trade.Buy(1.0);
}
//+------------------------------------------------------------------+
//| Cierre de compras                                                 |
//+------------------------------------------------------------------+
void CAdapterMACD::SupportBuy(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.ShortSignal())
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Ventas                                                         |
//+------------------------------------------------------------------+
void CAdapterMACD::InitSell(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.ShortSignal())
      Trade.Sell(1.0);
}
//+------------------------------------------------------------------+
//| Cierre de compras                                                 |
//+------------------------------------------------------------------+
void CAdapterMACD::SupportSell(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.LongSignal())
      pos.CloseAtMarket();
}

La lógica descrita hace lo mismo que el ejemplo anterior: abre posiciones largas y cortas en el cruce del histograma MACD con su línea de señal. Sin embargo, como puede ver, el código se ha hecho aún más corto. A diferencia de la primera versión del trabajo con la señal, ahora no es necesario establecer la dirección de la señal y actualizar los valores de los indicaores cada vez. Asimismo, tampoco es necesario ubicar objetos auxiliares en el código de la estrategia.  Todas estas acciones las realizará por nosotros el adaptador.

 

Unión de varias señales en el código de una estrategia comercial

¿Es posible usar diferentes patrones e incluso señales para entrar y salir del mercado?  Esta es la primera pregunta que nos viene a la cabeza al analizar el funcionamiento de las señales. La respuesta a esta pregunta es afirmativa. Después de obtener acceso completo al sistema de señales, podemos usar no solo un patrón, sino varios a la vez. Para que los patrones no se mezclen los unos con los otros, el adaptador de señales permite establecer solo un patrón para su uso. Sin embargo, los propios adaptadores de señales pueden existir en una cantidad ilimitada. En este caso, cada patrón se presentará con su adaptador y señal, incluso si todos los patrones tienen una sola señal como base. Está claro que desde el punto de vista de los recursos se trata de un método un poco menos efectivo que el propuesto en la biblioteca estándar, pero tiene sus ventajas.

Como ejemplo, escribiremos una estrategia que adoptará diferentes señales para la entrada y la salida del mercado. Como entrada se usarán los patrones del indicador RSI, basados en la sobrecompra y sobreventa. Como salida, usaremos el segundo patrón del indicador Accelerator Iscillator (AC), propuesto por Bill Williams. Vamos a mostrar las reglas de la estrategia con más detalle.

Compra: Viraje más allá del nivel de sobreventa - el oscilador ha virado hacia arriba y su valor en la barra analizada se encuentra más allá del nivel de sobreventa (por defecto es 30):

 

Fig. 5. Condiciones de entrada en una posición larga 

 

Venta: Viraje más allá del nivel de sobrecompra - el oscilador ha virado hacia abajo y su valor en la barra analizada se encuentra más allá del nivel de sobrecompra (por defecto es 70):

 

Fig. 6. Condiciones de entrada en una posición corta

Cierre de compra: El valor del indicador AC está por encima de 0, y disminuye en la barra analizada y las dos anteriores:


Fig. 7. Condiciones de salida de una posición larga 

Cierre de venta: El valor del indicador AC está por debajo de 0, y aumenta en la barra analizada y las dos anteriores.

 

Fig. 8. Condiciones de salida de una posición corta

Notemos que para la salida de una posición larga se usa el patrón de la señal AC para la entrada en la posición corta, y al contrario, para la salida de la posición corta se usa el patrón de la señal AC para la entrada en la posición larga.

El experto que implementa esta lógica se muestra en el listado de más abajo:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/es/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/ru/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Strategy\SignalAdapter.mqh>
input int RSI_Period = 14; // RSI Period
//+------------------------------------------------------------------+
//| La estrategia recibe los eventos y los muestra en el terminal.             |
//+------------------------------------------------------------------+
class COnSignal_RSI_AC : public CStrategy
{
private:
   CSignalAdapter    m_adapter_rsi;
   CSignalAdapter    m_adapter_ac;
public:
                     COnSignal_RSI_AC(void);
   virtual void      InitBuy(const MarketEvent &event);
   virtual void      InitSell(const MarketEvent &event);
   virtual void      SupportBuy(const MarketEvent& event, CPosition* pos);
   virtual void      SupportSell(const MarketEvent& event, CPosition* pos);
};
//+------------------------------------------------------------------+
//| Inicialización del módulo de señales CSignalMacd                        |
//+------------------------------------------------------------------+
COnSignal_RSI_AC::COnSignal_RSI_AC(void)
{
   MqlSignalParams params;
   params.every_tick = false;
   params.magic = 32910;
   params.point = 10.0;
   params.symbol = Symbol();
   params.period = Period();
   params.usage_pattern = 2;
   params.signal_type = SIGNAL_AC;
   CSignalAC* ac = m_adapter_ac.CreateSignal(params);
   params.usage_pattern = 1;
   params.magic = 32911;
   params.signal_type = SIGNAL_RSI;
   CSignalRSI* rsi = m_adapter_rsi.CreateSignal(params);
   rsi.PeriodRSI(RSI_Period);
}
//+------------------------------------------------------------------+
//| Compras.                                                         |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::InitBuy(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(positions.open_buy > 0)
      return;
   if(m_adapter_rsi.LongSignal())
      Trade.Buy(1.0);
}
//+------------------------------------------------------------------+
//| Cierre de compras                                                 |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::SupportBuy(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_adapter_ac.ShortSignal())
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Ventas.                                                         |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::InitSell(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(positions.open_sell > 0)
      return;
   if(m_adapter_rsi.ShortSignal())
      Trade.Sell(1.0);
}
//+------------------------------------------------------------------+
//| Cierre de compras                                                 |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::SupportSell(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_adapter_ac.LongSignal())
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+

Preste atención a que este experto posee un parámetro externo que permite establecer el periodo del indicador RSI. Esto se hace en el constructor de la estrategia, a través del acceso inmediato a la señal.

El resultado del funcionamiento de esta estrategia se muestra en el gráfico de más abajo:

 

Fig. 8. Resultado del funcionamiento de la estrategia 

En el gráfico se ve que el experto funciona con dos indicadores: RSI y AC. La entrada tiene lugar cuando RSI comienza a aumentar o disminuir en las zonas de su sobrecompra o sobreventa. Estas zonas están rodeadas con círculos rojos. La salida de la posición tiene lugar cuando el indicador AC forma tres líneas de un mismo color. Además, es importante que para la salida de la compra las líneas sean de color rojo y se encuentren por encima del nivel cero. Para las ventas las líneas deberán ser verdes y encontrarse por debajo del nivel del cero. Estos momentos han sido destacados con rectángulos azules.

El gráfico nos muestra que la lógica del experto se procesa de forma correcta. Las reglas comerciales del experto no son triviales. Además, la propia estrategia apenas ocupa espacio. Estas son las ventajas del nuevo uso del código.

 

Conclusión

Hemos analizado el método de integración de la biblioteca sistémica de señales en el motor comercial CStrategy. Gracias a esta integración, en CStrategy es muy sencillo escribir estrategias basadas en los patrones de las señales estándar. Además, cualquier señal escrita para el autogenerador de estrategias MetaTrader se hace automáticamente accesible para el motor comercial CStrategy.

Gracias a que las señales comerciales ya son accesibles en CStrategy, el usuario puede ahorrar una cantidad considerable de tiempo. Ahora no será necesario escribir indicadores y algoritmos propios para determinar patrones, si ya han sido implementados en el módulo de señales de estándar. Además, esta nueva capacidad reduce significativamente la complejidad de la escritura de estrategias, puesto que la determinación de patrones tan complejos como la divergencia o la divergencia doble ahora se delega en soluciones ya preparadas.