Asesor Experto multiplataforma: Señales

10 julio 2017, 10:36
Enrico Lambino
0
2 622

Índice

Introducción

En el artículo anterior, hemos analizado la clase COrderManager y su aplicación para automatizar los procesos de la apertura y cierre de las operaciones. Con el fin de automatizar la generación de las señales, en este artículo estamos usando más o menos los principios semejantes. Se puede conseguir eso usando la clase CSignal y su contenedor, la clase CSignals. El presente artículo describe en detalle la implementación de los objetos de estas clases.

Objetivos

Las clases CSignal y CSignals, de las que vamos a hablar, persiguen los siguientes objetivos:

  • Compatibilidad de la implementación con MQL4 y con MQL5
  • Automatización de la mayoría de los procesos relacionados con la valoración de las señales comerciales
  • Sencillez de la implementación

Señales comerciales

Tanto CSignal, como su clase contenedor CSignals, se encargan de evaluar todas las señales tomando en cuenta el estado actual del mercado. Las señales comerciales se dividen en dos grupos principales: señales de entrada y señales de salida. Para que una señal de entrada haga que el Asesor Experto (EA) ejecute una operación, tiene que tener la misma dirección que las demás señales de entrada (todas largas, o todas cortas). En cuanto a las señales de salida, cada señal es independiente y puede influir en el resultado final sólo por sí misma, dependiendo de su propio resultado. Además, las señales de salida se evalúan conjuntamente. Por ejemplo, si la señal № 1 señaliza sobre las condiciones del cierre de todas las posiciones sell, y la señal № 2, sobre las condiciones del cierre de todas las posiciones buy, el resultado de esta conjunción puede ser el cierre de todas las operaciones.

Tipos de señales

El EA tiene cuatro tipos diferentes de la señal que se interpretan por los objetos de las señales (y por consecuencia de eso, por el EA) a base de que si suponen la entrada o la salida del mercado. La siguiente tabla describe los tipos de las señales, sus valores y la interpretación dependiendo del tipo. 

 Tipo de señal
 Valor  Entrada Salida
CMD_VOID -1
Cancela todas las demás señales
Salida de todas las operaciones
CMD_NEUTRAL 0
Se ignora
Se ignora
CMD_LONG 1
Apertura de una posición larga
Salida de operaciones cortas
CMD_SHORT 2
Apertura de una posición corta
Salida de operaciones largas

Los tipos CMD_LONG y CMD_SHORT son bastante fáciles para la comprensión, así que vamos a considerar más detalladamente otros dos tipos de señales.

CMD_VOID tiene el valor entero -1 y se refiere a la señal que está en una fuerte discrepancia con la actual. Si es una señal de salida, entonces cancela los resultados de todas las demás señales de entrada. Eso quiere decir que su resultado es obligatorio, y si él dice sobre las condiciones desfavorables para el trading, eso va a significar las condiciones no comerciales para todas las demás señales, independientemente de sus resultados separados y la dirección. Como ejemplo, veremos la siguiente situación con tres señales de entrada:

Señal 1: CMD_VOID

Señal 2: CMD_LONG

Señal 3: CMD_SHORT

Resultado: CMD_VOID

Vemos que la señal 1 cancela otras dos, y el resultado final será CMD_VOID. Pero nótese que las señales 2 y 3 son tienen la dirección contraria. De esta manera, independientemente del valor de la señal 1, el resultado final será una situación no comercial para el EA.

Ahora, vamos a considerar una situación un poco modificada, como se muestra a continuación:

Señal 1: CMD_VOID

Señal 2: CMD_LONG

Señal 3: CMD_LONG

Resultado: CMD_VOID

En este caso, las señales 2 y 3 van en la misma dirección, pero la señal 1 anula todas las demás. En conclusión, la señal en total muestra que la situación es desfavorable para el trading. La señal 1 con su estado de invalidez obtiene más peso incluso si las demás señales tienen la misma dirección.

Pero si estamos buscando la señal para salir del mercado, el segundo ejemplo demuestra la salida de todas las posiciones: las tres señales están de acuerdo con el cierre de las posiciones largas, y la señal 1 ordena cerrar las posiciones largas y las cortas también. Puesto que todas las señales de salida se avalúan en conjunto, el resultado final provocará el cierre de todas las operaciones.

CMD_NEUTRAL tiene el valor entero 0 y significa la falta de la señal. Equivale aproximadamente a la opción «abstenerse» en el proceso de elección. La señal «neutral» renuncia a su derecho de influir en el resultado final, y deja la decisión a las demás señales. Sin embargo, si tenemos una sola señal y ella «se abstiene», para el EA se forma una condición sin salidas y entradas que es semejante a la situación si varias señales tuvieran la dirección diferente.

Ahora veremos la situación con el uso de CMD_NEUTRAL, modificando un poco las condiciones del primer ejemplo de este apartado:

Señal 1: CMD_NEUTRAL

Señal 2: CMD_LONG

Señal 3: CMD_SHORT

Resultado: CMD_VOID

En nuestro tercer ejemplo, la señal 1 da una evaluación neutral. En este caso, cuando se obtiene el resultado final, se toman en cuenta sólo las señales 2 y 3. Y puesto que son de direcciones opuestas, el resultado final será una situación no comercial.

La situación es diferente si las señales restantes tienen la misma dirección. En nuestro cuarto ejemplo (se muestra a continuación), la primera señal es neutral y las demás tienen la misma dirección. En este caso, la señal 1 se ignora, las señales 2 y 3 se evalúan y dan una señal conjunta de compra como resultado final.

Señal 1: CMD_NEUTRAL

Señal 2: CMD_LONG

Señal 3: CMD_LONG

Resultado: CMD_LONG

Nótese que el orden de señales no importa, y el siguiente conjunto de señales:

Señal 1: CMD_NEUTRAL

Señal 2: CMD_LONG

Señal 3: CMD_LONG

Señal 4: CMD_NEUTRAL

Señal 5: CMD_LONG

Señal 6: CMD_NEUTRAL

al final dará la señal conjunta CMD_LONG, en vez de CMD_NEUTRAL.

Si se trata de una señal de salida, la señal con el resultado CMD_NEUTRAL no influirá en el resultado final. Al determinar la señal final, todo va a ser como si no existiera en absoluto.

Es importante que el valor asignado de CMD_NEUTRAL es igual a 0, y que usamos esta enumeración personalizada como sustitución para ENUM_ORDER_TYPE. El uso de esta enumeración tiene unas ventajas evidentes.

Primero, podemos configurar mejor la interpretación de la señal.

Otra ventaja consiste en que podemos prevenir una ejecución casual de las operaciones provocada por las variables no inicializadas. Por ejemplo, ORDER_TYPE_BUY tiene el valor entero 0. Si nuestro EA pasa la variable int directamente al método que procesa la solicitud comercial, y esta variable no está inicializada o no tiene asignado otro valor (lo más probable por casualidad), entonces el valor predefinido será igual a 0, lo que al final llevará a la colocación de una orden de compra. Por otro lado, en caso de la enumeración personalizada, este accidente nunca ocurrirá, ya que el valor cero para la variable siempre significará una situación no comercial.

Comparación con CExpertSignal

CExpertSignal evalúa la dirección general de la siguiente manera:

  1. Calcula su propia dirección y la guarda en la variable m_direction
  2. Para cada uno de los filtros
    1.  obtiene su dirección,
    2.  añade la dirección a m_direction (o la resta si el filtro es inverso)
  3. Si el valor final de m_direction supera el valor de umbral, aparece una señal comercial.

Usando este método, podemos deducir que cuanto más positivo sea el valor m_direction, más señales dicen de que el precio puede crecer (se aumenta la posibilidad de sobrepasar el valor de umbral). Asimismo, cuanto más negativo sea este valor, más señales predirán la caída del precio. El valor de umbral siempre es positivo, y por eso el valor absoluto de m_directionse usa durante la comprobación de la señal de venta.

Los objetos de señal presentados en este artículo pueden ser considerados como la versión simplificada de CExpertSignal. No obstante, en vez de evaluar la señal y sus filtros juntamente usando las operaciones aritméticas, cada señal se considera por separado. Este enfoque es menos universal, pero da al trader o al programador más control sobre la influencia de cada señal sobre la señal final.

Fases

OnInit y OnDeinit

La fase de inicialización de cada señal a menudo está relacionada con la creación e inicialización de los indicadores que se usan en ella, así como con los miembros adicionales de la clase (si existen) que pueden ser necesarios para diferentes métodos que se encuentran en la clase del objeto. Durante la deinicialización, las instancias del indicador tienen que eliminarse.

OnTick

  1. La fase previa (Cálculo) — se actualizan los valores necesarios para los cálculos (comprobación de la señal).
  2. Fase principal (Comprobación de la Señal) — durante esta fase, se determina el estado actual de la señal. Es mejor que el cuerpo de este método tenga una sola línea del código para mejorar su legibilidad (para que se vea al instante qué es lo que exactamente hace la señal).
  3. Fin (o Fase de Actualización) — determinados elementos de algunas señales pueden ser actualizados sólo después de la realización real de la comprobación de la señal. El ejemplo de eso es el seguimiento del valor del precio bid anterior con el fin de compararlo con el precio bid actual o con otros valores (es posible hacerlo desde el objeto del gráfico o el valor del indicador). La actualización de la variable que guarda el valor anterior bid durante la fase previa no tiene sentido porque su valor siempre va a ser igual al valor actual durante la comprobación de la señal.

Cabe mencionar que en MQL5 hay un array de ticks, y MQL4 no dispone de esta función. Aquí, los estándares de MQL4 a los que el código está vinculado serán como un factor limitativo para asegurar la compatibilidad multiplataforma. El factor determinante será la implementación separadora.

Implementación

Clase CSignal

Antes de comprobar cualquier señal comercial, primero hace falta actualizar los datos necesarios para los cálculos. Eso se consigue a través del método Refresh de la clase CSignal, donde los indicadores (así como los datos de las series temporales) se actualizan hasta los últimos valores. En el siguiente fragmento del código, se muestra el método Refresh de la clase CSignal:

bool CSignalBase::Refresh(void)
  {
   for(int i=0;i<m_indicators.Total();i++)
     {
      CSeries *indicator=m_indicators.At(i);
      if(indicator!=NULL)
         indicator.Refresh(OBJ_ALL_PERIODS);
     }
   return true;
  }

La llamada real al método Refresh de la clase CSignal se realiza dentro del método Check de la misma clase. Como se muestra en el código de abajo, este método finaliza el procesamiento posterior si los datos no pueden actualizarse (de lo contrario, las señales no serán precisas).

void CSignalBase::Check(void)
  {
   if(!Active())
      return;
   if(!Refresh())
      return;
   if(!Calculate())
      return;
   int res=CMD_NEUTRAL;
   if(LongCondition())
     {
      if (Entry())
         m_signal_open=CMD_LONG;
      if (Exit())
         m_signal_close=CMD_LONG;
     }
   else if(ShortCondition())
     {
      if (Entry())
         m_signal_open=CMD_SHORT;
      if (Exit())
         m_signal_close=CMD_SHORT;
     }
   else
   {
      if (Entry())
         m_signal_open=CMD_NEUTRAL;
      if (Exit())
         m_signal_close=CMD_NEUTRAL;
   }
   if(m_invert)
     {
      SignalInvert(m_signal_open);
      SignalInvert(m_signal_close);
     }
   Update();
  }

Dentro del método Check de la clase CSignal, la señal actual se determina por la llamada a los métodos LongCondition y ShortCondition que parecen aproximadamente a los métodos que se aplican en la Librería estándar de MQL5.

La devolución de la señal actual se consigue llamando a los métodos CheckOpenLong y CheckOpenShort que deben invocarse fuera de la clase (desde la otra clase o directamente dentro de la función OnTick):

bool CSignalBase::CheckOpenLong(void)
  {
   return m_signal_open==CMD_LONG;
  }
bool CSignalBase::CheckOpenShort(void)
  {
   return m_signal_open==CMD_SHORT;
  }

CSignal por sí mismo no da ningunas señales de compra o de venta. De esta manera, los métodos son virtuales, y obtendrán la implementación real sólo cuando CSignal será extendida.

virtual bool      LongCondition(void)=0;
virtual bool      ShortCondition(void)=0;

Pero si la información desde los datos no procesados de las series temporales y los indicadores no es suficiente y hace falta realizar más cálculos, primero, hay que implementar el método Calculate, y sólo después de él, los métodos arriba descritos. Como los indicadores que serán utilizados en CSignal, las variables en las que se guardan los valores tendrán que ser los miembros de la clase también. Por eso, los métodos LongCondition y ShortCondition van a asegurar el acceso a estas variables.
virtual bool      Calculate(void)=0;
virtual void      Update(void)=0;

Obsérvese que el método Calculate se refiere al tipo booleano (bool), y el método Update no devuelve ningún valor. Eso significa que se puede configurar el EA de tal manera que la comprobación de las señales se quede cancelada si la ejecución de cálculos determinados ha falado. El método Update tiene el tipo void, y ya no hace falta asignarle el tipo booleano porque él se invoca después de la llegada de las señales actuales.

Una instancia particular de CSignal puede ser creada con el fin de mostrar los resultados de las señales de entrada, de salida o las señales de ambos tipos. Se puede conseguirlo mediante la alteración de los métodos Entry() y Exit() de la clase. Eso normalmente ocurre cuando el EA se inicializa.

Clase CSignals

CSignals es descendiente de CArrayObj. Eso le permite guardar las instancias de СObject, que en este caso van a almacenar las instancias de CSignal.

La inicialización de esta clase supone el traspaso del objeto CSymbolManager que ha sido discutido en uno de los artículos anteriores. Eso permitirá a otras señales obtener los datos necesarios, tanto a partir del símbolo en el gráfico, como a partir de otro símbolo. Además, durante la acción de este método se invoca el método Init para cada señal:

bool CSignalsBase::Init(CSymbolManager *symbol_man)
  {
   m_symbol_man= symbol_man;
   m_event_man = aggregator;
   if(!CheckPointer(m_symbol_man))
      return false;
   for(int i=0;i<Total();i++)
     {
      CSignal *signal=At(i);
      if(!signal.Init(symbol_man))
         return false;
     }
   return true;
  }

El método Check inicializa la señal como neutral, y luego recorre en el ciclo cada una de las señales para obtener su valor actual. Si el método obtiene una señal inválida o una señal válida (de compra o de venta) pero que se diferencia de la señal válida anterior, entonces la respuesta final se determina por la señal que cancela el trading. 

CSignalsBase::Check(void)
  {
   if(m_signal_open>0)
      m_signal_open_last=m_signal_open;
   if(m_signal_close>0)
      m_signal_close_last=m_signal_close;
   m_signal_open=CMD_NEUTRAL;
   m_signal_close=CMD_NEUTRAL;
   for(int i=0;i<Total();i++)
     {
      CSignal *signal=At(i);      
      signal.Check();
      if(signal.Entry())
        {
         if(m_signal_open>CMD_VOID)
           {
            ENUM_CMD signal_open=signal.SignalOpen();
            if(m_signal_open==CMD_NEUTRAL)
              {    
               m_signal_open=signal_open;
              }
            else if(m_signal_open!=signal_open)
              {               
               m_signal_open=CMD_VOID;
              }
           }
        }
      if(signal.Exit())
        {
         if(m_signal_close>CMD_VOID)
           {
            ENUM_CMD signal_close=signal.SignalClose();
            if(m_signal_close==CMD_NEUTRAL)
              {
               m_signal_close=signal_close;
              }
            else if(m_signal_close!=signal_close)
              {
               m_signal_close=CMD_VOID;
              }
           }
        }
     }
   if(m_invert)
     {
      CSignal::SignalInvert(m_signal_open);
      CSignal::SignalInvert(m_signal_close);
     }
   if(m_new_signal)
     {
      if(m_signal_open==m_signal_open_last)
         m_signal_open = CMD_NEUTRAL;
      if(m_signal_close==m_signal_close_last)
         m_signal_close= CMD_NEUTRAL;
     }
  }

Instancias del indicador

Cada instancia de CSignal tiene su propio conjunto de instancias del indicador, que se almacenan en m_indicators (una instancia de CIndicators). Idealmente, cada instancia del indicador perteneciente a una determinada instancia de CSignal será independiente de cualquier otra instancia de CSignal. Es una desviación del método utilizado en la Librería estándar MQL5, que almacena todos los indicadores utilizados por el EA en una instancia de CIndicators, que forma parte de la clase de CExpert. A pesar de que este enfoque tienda a duplicar los objetos (por ejemplo, el objeto del indicador МА para la señal 1, y luego el mismo objeto del indicador para la señal 2), y por consiguiente, duplicar los cálculos, dispone de unas ciertas ventajas.

Como mínimo, considera los objetos de las señales mayoritariamente como unas unidades independientes. Eso da más libertad a la señal en cuanto al uso de los indicadores, sobre todo al seleccionar un símbolo y timeframe. Por ejemplo, para los EAs que trabajan con varios pares de divisas, puede ser complicado desarrollar los indicadores para procesar los datos de otros instrumentos, si se aplican sólo las clases del EA de la Librería estándar. Probablemente, la manera más fácil de evitar eso (en vez cambiar o extender constantemente CExpert) consiste en escribir un nuevo indicador personalizado (que asegura el acceso a los datos necesarios y/o que los procesa), que luego puede usarse por la clase CExpert para sus filtros y/o señales.

Limitaciones

  1. Disponibilidad de los indicador No todos los indicadores disponibles en MetaTtrader 4 también están disponibles en MetaTrader 5 (y viceversa). Por eso, si necesitamos un EA multiplataforma que trabaja en ambas plataformas, los indicadores para MetaTrader 4 deben ser compatibles con Metatrader 5. De lo contrario, el EA no estará disponible para el uso en otra plataforma. Habitualmente, eso no supone problemas para los indicadores estándar, salvo algunas excepciones (por ejemplo, el indicador de volúmenes en MT4 se diferencia de la versión MT5). Los indicadores personalizados deben estar implementados en dos versiones para asegurar el trabajo multiplataforma.
  2. Disponibilidad de determinados datos Algunos datos de las series temporales simplemente no están disponibles en MetaTrader 4. Por eso, es difícil o incluso imposible transformar algunas estrategias basadas en los datos disponibles sólo en MetaTrader 5 (por ejemplo, el volumen de tick) en el código en MQL4.

Ejemplos

Ejemplo #1: muestra del Gestor de órdenes

En nuestro artículo anterior, hemos mostrado el ejemplo del EA para ver cómo trabaja el Gestor de órdenes en el EA actual. La metodología de trading en este EA consiste en la alteración entre las operaciones de compra y de venta al principio de cada barra nueva. El fragmento del código de abajo muestra la función OnTick en este EA:

void OnStart()
  {
//--- 
   static int bars = 0;
   static int direction = 0;
   int current_bars = 0;
   #ifdef __MQL5__
      current_bars = Bars(NULL,PERIOD_CURRENT);
   #else 
      current_bars = Bars;
   #endif
   if (bars<current_bars)
   {   
      symbol_info.RefreshRates();
      COrder *last = order_manager.LatestOrder();
      if (CheckPointer(last) && !last.IsClosed())
         order_manager.CloseOrder(last);
      if (direction<=0)
      {
         Print("Entering buy trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
         direction = 1;
      }
      else
      {
         Print("Entering sell trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
         direction = -1;
      }   
      bars = current_bars;
   }
  }

Como podemos ver, las líneas del código responsables del comportamiento del EA (las que se refieren a la generación de señales) se encuentran en diferentes lugares de la función. Para los EAs tan simples como éste, el código es fácil para descifrar, y por consiguiente, para introducir los cambios. Sin embargo, el mantenimiento del código puede hacerse más difícil según vaya haciéndose más complejo el propio EA. Nuestro objetivo consiste en organizar la generación de señales para este EA, usando las clases que estamos considerando en el presente artículo.

Para eso, necesitamos extender la clase CSignal en el archivo principal de cabecera usando tres miembros protegidos: (1) número de barras anteriores, (2) número anterior de las barras anteriores y (3) la dirección actual, tal como se muestra en el siguiente código:

class SignalOrderManagerExample: public CSignal
  {
protected:
   int               m_bars_prev;
   int               m_bars;
   int               m_direction;
   // resto de la clse

Además, necesitamos extender los métodos que se encuentran en la clase CSignal. Para el método Calculate, nos hace falta usar también el mismo método de cálculos que en el ejemplo antiguo:

bool SignalOrderManagerExample::Calculate(void)
  {
   #ifdef __MQL5__
      m_bars=Bars(NULL,PERIOD_CURRENT);
   #else
      m_bars=Bars;
   #endif
   return m_bars>0 && m_bars>m_bars_prev;
  }

Los métodos para obtener el número de barras en el gráfico actual son diferentes en dos plataformas, y por eso, vamos a partir la implementación, igual como lo hemos hecho en el ejemplo antiguo. Además, nótese que el método Calculate es una variable booleana. Como ya hemos discutido antes, si el método Сalculate devuelve false, el procesamiento posterior de señales para este evento de tick se detiene. Aquí, definimos explícitamente dos reglas cuando el procesamiento posterior de la señal para el evento de tick debe ser concluido: (1) número actual de barras más de cero y (2) número actual de barras más que el anterior. 

Luego, vamos a considerar el método Update de la clase, extendiendo el método de nuestra clase personalizada que se muestra en las siguientes líneas del código:

void SignalOrderManagerExample::Update(void)
  {
   m_bars_prev=m_bars;
   m_direction= m_direction<=0?1:-1;
  }


Después de comprobar las señales, actualizamos el número anterior de las barras (m_bars_prev) hasta el número actual (m_bars). Además, actualizamos la dirección. Si el valor actual es menos o igual a cero (la dirección anterior es la venta, o la operación se ejecuta por primera vez), el valor nuevo de esta variable será igual a 1. De lo contrario, su valor será -1.

Finalmente, trabajamos con la generación de las propias señales. A base de las variables necesarias para la evaluación de las señales para el tick actual, determinamos las condiciones que, en su lugar, determinan cuando la señal obtenida debe ser una señal de compra o de venta. Eso se alcanza mediante la extensión de los métodos LongCondition y ShortCondition de la clase CSignal:

bool SignalOrderManagerExample::LongCondition(void)
  {
   return m_direction<=0;
  }

bool SignalOrderManagerExample::ShortCondition(void)
  {
   return m_direction>0;
  }

La función Init para este ejemplo es muy parecida a la del ejemplo anterior. Además de eso, en este ejemplo tenemos que crear el descendiente de la clase CSignal que acabamos de definir (SignalOrderManagerExample), así como su contenedor (CSignals):

int OnInit()
  {
//--- 
   order_manager=new COrderManager();
   symbol_manager=new CSymbolManager();
   symbol_info=new CSymbolInfo();
   if(!symbol_info.Name(Symbol()))
      Print("symbol not set");
   symbol_manager.Add(GetPointer(symbol_info));
   order_manager.Init(symbol_manager,NULL);
   SignalOrderManagerExample *signal_ordermanager=new SignalOrderManagerExample();
   signals=new CSignals();
   signals.Add(GetPointer(signal_ordermanager));
//--- 
   return(INIT_SUCCEEDED);
  }

Aquí, declaramos signal_ordermanager como puntero a un objeto nuevo tipo SignalOrderManagerExample, que acabamos de definir. Luego, haremos lo mismo para CSignals a través del puntero de señales, y después añadimos el puntero a SignalOrderManagerExample a él, llamando a su método Add.

El uso de CSignal y CSignals en nuestro EA hará que la función OnTick sea más simple:

void OnStart()
  {
//--- 
   symbol_info.RefreshRates();   
   signals.Check();
   if(signals.CheckOpenLong())
     {
      close_last();
      Print("Entering buy trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
     }
   else if(signals.CheckOpenShort())
     {
      close_last();
      Print("Entering sell trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
     }
  }

Todos los demás cálculos necesarios para la generación de la señal actual han sido desplazados en los objetos de las clases CSignal y CSignals. De esta manera, todo lo que tenemos que realizar es hacer que CSignals ejecute la comprobación, y luego obtener su resultado a través de la llamada a los métodos CheckOpenLong y CheckOpenShort. Las capturas de pantalla de abajo muestran los resultados de la simulación del EA en la plataforma MetaTrader 4 y MetaTrader 5:

(MT4)

signal_ordermanager (MT4)

(MT5)

signal_ordermanager (MT5)

Ejemplo #2: Asesor Experto MA

Nuestro siguiente ejemplo demuestra el uso del indicador MA en la evaluación de las señales comerciales. MA es un indicador estándar para ambas plataformas, así como uno de los más simples para el uso en el desarrollo de un EA multiplataforma.

Igual como en el ejemplo anterior, creamos una señal personalizada extendiendo la clase CSignal:

class SignalMA: public CSignal
  {
protected:
   CiMA             *m_ma;
   CSymbolInfo      *m_symbol;
   string            m_symbol_name;
   ENUM_TIMEFRAMES   m_timeframe;
   int               m_signal_bar;
   double            m_close;   
   // resto de la clse

Como podemos ver, ambas librerías (MQL4 y MQL5) ya proporcionan los objetos de la clase para el indicador Moving Average. Eso simplifica la incorporación del indicador en nuestra clase personalizada de señal. A pesar de que eso no es necesario, en este ejemplo, también vamos a guardar el objeto destino a través de m_symbol, puntero al objeto CSybmolInfo. Además, vamos a declarar la variable m_close en la que va a almacenarse el valor del precio de cierre de la barra de señal. Los demás miembros protegidos de la clase son los parámetros del indicador MA.

El ejemplo anterior no tiene la estructura compleja de datos que es necesario preparar antes de usar. Este ejemplo sí que la tiene (es el propio indicador). Por eso tendremos que inicializarlo en el constructor de la clase:

void SignalMA::SignalMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int period,const int shift,const ENUM_MA_METHOD method,const ENUM_APPLIED_PRICE applied,const int bar)
  {
   m_symbol_name= symbol;
   m_timeframe = timeframe;
   m_signal_bar = bar;
   m_ma=new CiMA();
   m_ma.Create(symbol,timeframe,period,0,method,applied);
   m_indicators.Add(m_ma);   
  }

La señal obtenida de la media móvil a menudo se compara con algún precio en el gráfico. Eso puede ser un tipo especificado, por ejemplo, open o close, o bien los precios actuales bid/ask. En el último caso, necesitaremos el objeto del símbolo para nuestro trabajo. En nuestro ejemplo, vamos a extender el método Init, para inicializar la obtención del símbolo correcto a usar desde CSymbolManager. Eso será útil si queremos comparar МА con el precio bid/ask, y no con los datos OHLC y sus derivados. 

bool SignalMA::Init(CSymbolManager *symbol_man,CEventAggregator *event_man=NULL)
  {
   if(CSignal::Init(symbol_man,event_man))
     {
      if(CheckPointer(m_symbol_man))
        {
         m_symbol=m_symbol_man.Get();
         if(CheckPointer(m_symbol))
            return true;
        }
     }
   return false;
  }

El siguiente método extendido es el método Сalculate:

bool SignalMA::Calculate(void)
  {
   double close[];
   if(CopyClose(m_symbol_name,m_timeframe,signal_bar,1,close)>0)
     {
      m_close=close[0];
      return true;
     }   
   return false;
  }

No es necesario actualizar más los datos del indicador, puesto que eso ya se realiza dentro del método Refresh de la clase CSignal. De manera alternativa, también podemos implementar el descendiente de la clase CSignal, con el fin de obtener el precio de cierre de la barra de señal usando la clase CCloseBuffer. Él también es descendiente de la clase CSeries, así que podemos añadirlo a m_indicators, para que la instancia de CCloseBuffer se actualice junto con otros indicadores. En este caso, ya no habrá que extender los métodos Refresh o Calculate de la clase CSignal. 

Para eso no es necesario extender adicionalmente el método Update, por eso vamos a proceder directamente a la generación de la señal. Los fragmentos del código de abajo muestran los métodos LongCondition y ShortCondition:

bool SignalMA::LongCondition(void)
  {
   return m_close>m_ma.Main(m_signal_bar);
  }

bool SignalMA::ShortCondition(void)
  {
   return m_close<m_ma.Main(m_signal_bar);
  }

Las condiciones son muy simples: si el precio de cierre de la barra de señal es mayor que el valor de MA en esta barra es la señal de compra. Y viceversa, si el precio de cierre es menor, obtenemos la señal de venta.

Igual como en el ejemplo anterior, simplemente inicializamos todos los demás punteros necesarios, y luego añadimos la instancia de CSignal a su contenedor (instancia de CSignals). Los códigos de abajo muestran el código adicional que es necesario para la inicialización de la señal OnInit:

SignalMA *signal_ma=new SignalMA(Symbol(),(ENUM_TIMEFRAMES) Period(),maperiod,0,mamethod,maapplied,signal_bar);
signals=new CSignals();
signals.Add(GetPointer(signal_ma));
signals.Init(GetPointer(symbol_manager),NULL);

El siguiente código muestra la función OnTick que es similar a la función OnTick del ejemplo anterior:

void OnStart()
  {
//--- 
   symbol_info.RefreshRates();
   signals.Check();
   if(signals.CheckOpenLong())
     {
      close_last();
      Print("Entering buy trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
     }
   else if(signals.CheckOpenShort())
     {
      close_last();
      Print("Entering sell trade..");
      order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
     }
  }

Las capturas de pantalla de abajo muestran los resultados de la simulación del EA en MT4 y MT5. Como podemos ver, los EAs trabajan siguiendo la misma lógica:

(MT4)

signal_ma (MT4)

(MT5)

signal_ma (MT5)

Ejemplo #3: Asesor Experto HA

En el siguiente ejemplo, intentaremos usar el indicador Heiken Ashi (HA) el en Asesor Experto. A diferencia del indicador МА, НА es un indicador personalizado, así que el desarrollo del EA en su base será más complicado que en el ejemplo anterior, puesto que además necesitaremos declarar la clase para el indicador Heiken Ashi a través de la extensión de CiCustom. Para empezar, vamos a considerar la definición de la clase para CiHA, nuestro objeto de la clase para el indicador HA:

class CiHA: public CiCustom
  {
public:
                     CiHA(void);
                    ~CiHA(void);
   bool              Create(const string symbol,const ENUM_TIMEFRAMES period,
                            const ENUM_INDICATOR type,const int num_params,const MqlParam &params[],const int buffers);
   double            GetData(const int buffer_num,const int index) const;
  };

Necesitaremos extender dos métodos: Create y GetData. Vamos a redifinir el constructor de la clase para el método Create:

bool CiHA::Create(const string symbol,const ENUM_TIMEFRAMES period,const ENUM_INDICATOR type,const int num_params,const MqlParam &params[],const int buffers)
  {
   NumBuffers(buffers);
   if(CIndicator::Create(symbol,period,type,num_params,params))
      return Initialize(symbol,period,num_params,params);
   return false;
  }

Aquí, declaramos la cantidad de búferes que debe tener el indicador, y luego inicializar los parámetros pasados a ellos. Los parámetros del indicador se almacenan en la estructura (MqlParam).

Para el método GetData, la implementación en dos lenguajes es diferente. En MQL4, se hace la llamada directa a la función iCustom, que muestra el valor del indicador en una barras particular del gráfico. En МQL5, la llamada al indicador se procesa de otra manera. iCustom pasa el handle al indicador (parecido a como se hace en las operaciones de archivos). Para obtener el valor del indicador en MetaTrader 5 para una determinada barra, se usa este handle, y la función iCustom no se invoca. En este caso, separamos la implementación:

double CiHA::GetData(const int buffer_num,const int index) const
  {
   #ifdef __MQL5__
      return CiCustom::GetData(buffer_num,index);
   #else
      return iCustom(m_symbol,m_period,m_params[0].string_value,buffer_num,index);
   #endif
  }

Nótese que en este método, en la versión MQL5, simplemente devolvemos el resultado de la llamada al método padre (CiCustom). En caso de MQL4, el método padre (CiCustom) simplemente devuelve cero, y de esta manera, tenemos que extenderlo, invocando prácticamente la función MQL4 iCustom. Puesto que esta función MQL4 no utiliza la estructura (MqlParams) para almacenar los parámetros del indicador, su llamada va a ser casi siempre diferente para cada indicador personalizado.

En la extensión de CSignal para este EA, no hay muchas diferencias en comparación con los ejemplos anteriores. Para el constructor, simplemente redefinimos los argumentos del método con el fin de acomodarlos a los parámetros del indicador necesarios para la evaluación de la señal. Para esta señal en particular, usamos un solo indicador:

void SignalHA::SignalHA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int numparams,const MqlParam &params[],const int bar)
  {
   m_symbol_name= symbol;
   m_signal_bar = bar;
   m_ha=new CiHA();
   m_ha.Create(symbol,timeframe,IND_CUSTOM,numparams,params,4);
   m_indicators.Add(m_ha);
  }

También necesitamos separar la implementación para el método Calculate, porque los indicadores Heiken Ashi para MetaTrader 4 y MetaTrader 5 se diferencian en la parte de la ubicación de los búferes. Para la versión anterior, es Low/High, High/Low, Open y Close, que ocupan el primero (búfer № 0), el segundo, el tercero y el cuarto búfer. Para la versión MQL5, será Open, High, Low y Close. De esta manera, tenemos que considerar un búfer de acceso en particular, para obtener el valor de parte del indicador, dependiendo de la plataforma utilizada:

bool SignalHA::Calculate(void)
  {
   #ifdef __MQL5__
      m_open=m_ha.GetData(0,signal_bar);
   #else
      m_open=m_ha.GetData(2,signal_bar);
   #endif
      m_close=m_ha.GetData(3,signal_bar);
   return true;
  }

Para la versión MQL5, el precio de apertura de la vela HA se coge del primer búfer (búfer № 0), mientras que en la versión MQL4, el precio se encuentra en el tercer búfer (búfer № 2). El precio de cierre de la vela HA se encuentra en el cuarto búfer (búfer № 3) para ambas versiones, así que colocamos la declaración fuera de la directiva del preprocesador.

Para la evaluación de las señales, siempre tenemos que actualizar los métodos LongCondition y ShortCondition, dependiendo del criterio que va a usarse para evaluar los valores almacenados. Para eso, usamos un trabajo habitual con Heiken Ashi durante la comprobación si la barra de señal es bajista o alcista.

bool SignalHA::LongCondition(void)
  {
   return m_open<m_close;
  }

bool SignalHA::ShortCondition(void)
  {
   return m_open>m_close;
  }

La función OnTick para este EA será la misma que en los ejemplos anteriores, así que pasamos a la función OnInit:

int OnInit()
  {
//--- 
   order_manager=new COrderManager();
   symbol_manager=new CSymbolManager();
   symbol_info=new CSymbolInfo();
   if(!symbol_info.Name(Symbol()))
      Print("symbol not set");
   symbol_manager.Add(GetPointer(symbol_info));
   order_manager.Init(symbol_manager,NULL);

   MqlParam params[1];
   params[0].type=TYPE_STRING;
   #ifdef __MQL5__
      params[0].string_value="Examples\\Heiken_Ashi";
   #else
      params[0].string_value="Heiken Ashi";
   #endif
      SignalHA *signal_ha=new SignalHA(Symbol(),0,1,params,signal_bar);
   signals=new CSignals();
   signals.Add(GetPointer(signal_ha));
   signals.Init(GetPointer(symbol_manager),NULL);
//--- 
   return(INIT_SUCCEEDED);
  }

Aquí vemos que la ubicación del archivo ex4 del indicador Heiken Ashi es diferente, dependiendo de la versión de la plataforma que se usa. Puesto que MqlParams requiere que el primer parámetro a guardar tenga el nombre del indicador personalizado (sin extensión), tenemos que separar de nuevo la implementación al especificar el primer parámetro. En MQL5, el indicador se ubica por defecto en "Indicators\Examples\Heiken Ashi", mientras que en MQL4, se encuentra en "Indicators\Heiken Ashi".

Las capturas de pantalla de abajo muestran los resultados de la prueba del EA en las plataformas MetaTrader 4 y MetaTrader 5. Como vemos, a pesar de que los indicadores se diferencian en el modo de construcción en el gráfico, sin embargo, ambos indicadores tienen la misma lógica, y el EA en las dos versiones puede trabajar basándose en la misma lógica:

(MT4)

signal_ha (MT4)

(MT5)

signal_ha (MT5)

Ejemplo #4: Asesor Experto a base de HA y MA

Nuestro último ejemplo es la combinación de los indicadores MA y HA, incluida en el Asesor Experto. Hay pocas diferencias en este ejemplo. Simplemente añadimos la definición de las clases que se encuentran en el segundo y en el tercer ejemplo, y luego añadimos los punteros a las instancias de CSignalMA y CSignalHA a la instancia de CSignals. Abajo se muestra el resultado del uso de este EA en modo de testeo.

(MT4)

signal_ha_ma (MT4)

(MT5)

signal_ha_ma (MT5)

Conclusión

En este artículo, hemos analizado las clases CSignal y CSignals, que son los objetos de la clase y van a utilizarse por el EA multiplataforma para evaluar la señal general en un tick dado. Las clases arriba mencionadas han sido desarrolladas para los procesos relacionados con la evaluación de las señales, y están segregadas del resto del código del Asesor Experto.

Traducción del inglés realizada por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/en/articles/3261

Archivos adjuntos |
signals.zip (3907.49 KB)
Asesor Experto multiplataforma: Gestor de órdenes Asesor Experto multiplataforma: Gestor de órdenes

En este artículo se trata de la creación de un gestor de órdenes para el Asesor Experto multiplataforma. El gestor de órdenes se encarga de la apertura y del cierre de las órdenes y posiciones que realiza el Asesor Experto, así como de la ejecución del registro independiente sobre ellas, y estará disponible para ambas versiones del terminal.

Ejemplo del indicador que construye las líneas de soporte y resistencia Ejemplo del indicador que construye las líneas de soporte y resistencia

En este artículo se muestra el ejemplo de la implementación del indicador para construir las líneas de soporte y resistencia a base las condiciones formalizadas. Usted podrá no sólo aplicar el indicador, sino también comprenderá qué fácil implementar eso. Ahora Usted podrá formular personalmente las condiciones para construir las líneas que considere necesarias, haciendo pequeñas modificaciones en el código del indicador a su medida.

Creación de indicadores personalizados usando la clase CCanvas Creación de indicadores personalizados usando la clase CCanvas

En el artículo se analiza un ejemplo de creación de indicadores de dibujado personalizados con la ayuda de primivitas gráficas de la clase CCanvas.

Interfaces gráficas XI: Refactorización del código de la librería (build 14.1) Interfaces gráficas XI: Refactorización del código de la librería (build 14.1)

A medida que la librería va creciendo, es necesario optimizar de nuevo su código para reducir su tamaño. La versión de la librería descrita en este artículo se ha hecho aún más orientada a objetos. Eso ha mejorado la facilidad de comprensión del código. La descripción detallada de los últimos cambios permitirá al lector desarrollar la librería por sí mismo, según las necesidades que tenga.