English Русский 中文 Deutsch 日本語 Português
Recetas MQL5 - Señales comerciales de los canales móviles

Recetas MQL5 - Señales comerciales de los canales móviles

MetaTrader 5Ejemplos | 13 septiembre 2016, 15:36
2 188 0
Denis Kirichenko
Denis Kirichenko

Introducción

En mi artículo «Recetas MQL5 - Programando los canales móviles», mostré el método de construcción de los canales equidistantes, que con frecuencia son denominados móviles. Además, para resolver las tareas, recurrí al instrumento «Canal equidistante» y a las posibilidades de la POO.

En este artículo, nos centraremos en las señales que se pueden identificar mediante el uso de estos canales. En base a estas señales, trataremos de crear una estrategia comercial.

En MQL5 ya hay publicada una serie de artículos dedicados a la generación de señales comerciales que recurren a los módulos preparados de Biblioteca estándar. Espero que este artículo complemente el material y aumente el abanico de usuarios de las clases estándar.

A los que comienzan a familiarizarse con esta estrategia, les propongo estudiar el material partiendo de lo simple hacia lo complejo. En primer lugar, crearemos una estrategia básica, y después la complicaremos y completaremos en la medida de lo posible.


1. Indicador de canales equidistantes

En el artículo anterior sobre los canales móviles, el asesor construía por sí mismo los canales, creando objetos gráficos. Este enfoque, por un lado, facilita la tarea para los programadores, pero por otro lado, hace algunas cosas imposibles. Por ejemplo, si el asesor va a trabajar en el modo de optimización, no va a detectar objetos gráficos en el gráfico, ya que este no existirá en absoluto. De acuerdo con las limitaciones durante la simulación:


Objetos gráficos durante la simulación

Durante la simulación/optimización no se construyen objetos gráficos. De esta forma, al recurrir a las propiedades del objeto creado durante la simulación/optimización, el experto obtendrá valores cero.

Esta limitación se extiende a la simulación en el modo visual.


Por eso yo me he decidido por otro camino y he creado un indicador que representa tanto fractales, como el canal actual.

Este indicador se llama EquidistantChannels. En esencia, consta de dos bloques. En el primero se calculan los búferes de los fractales, en el segundo, los búferes del canal.

Vamos a ver el código del manejador del evento Calculate.

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- si en la llamada anterior no ha habido barras
   if(prev_calculated==0)
     {
      //--- resetear los búferes
      ArrayInitialize(gUpFractalsBuffer,0.);
      ArrayInitialize(gDnFractalsBuffer,0.);
      ArrayInitialize(gUpperBuffer,0.);
      ArrayInitialize(gLowerBuffer,0.);
      ArrayInitialize(gNewChannelBuffer,0.);
     }
//--- Cálculo para los fractales [start]
   int startBar,lastBar;
//---
   if(rates_total<gMinRequiredBars)
     {
      Print("Datos insuficientes para el cálculo");
      return 0;
     }
//---
   if(prev_calculated<gMinRequiredBars)
      startBar=gLeftSide;
   else
      startBar=rates_total-gMinRequiredBars;
//---
   lastBar=rates_total-gRightSide;
   for(int bar_idx=startBar; bar_idx<lastBar && !IsStopped(); bar_idx++)
     {
      //---
      if(isUpFractal(bar_idx,gMaxSide,high))
         gUpFractalsBuffer[bar_idx]=high[bar_idx];
      else
         gUpFractalsBuffer[bar_idx]=0.0;
      //---
      if(isDnFractal(bar_idx,gMaxSide,low))
         gDnFractalsBuffer[bar_idx]=low[bar_idx];
      else
         gDnFractalsBuffer[bar_idx]=0.0;
     }
//--- Cálculo para los fractales [end]

//--- Cálculo para los límites del canal [start]
   if(prev_calculated>0)
     {
      //--- si el conjunto no ha sido inicializado
      if(!gFracSet.IsInit())
         if(!gFracSet.Init(
            InpPrevFracNum,
            InpBarsBeside,
            InpBarsBetween,
            InpRelevantPoint,
            InpLineWidth,
            InpToLog
            ))
           {
            Print("¡Error de inicialización del conjunto de fractales!");
            return 0;
           }
      //--- cálculo
      gFracSet.Calculate(gUpFractalsBuffer,gDnFractalsBuffer,time,
                         gUpperBuffer,gLowerBuffer,
                         gNewChannelBuffer
                         );
     }
//--- Cálculo para los límites del canal [end]

//--- return value of prev_calculated for next call
   return rates_total;
  }

El bloque donde tiene lugar el cálculo de los valores de los búferes de fractales se destaca en color amarillo, y el bloque donde se calculan los búferes de canal, en color verde. Resulta sencillo notar que el segundo bloque se activará no en la primera llamada del manejador, sino en el siguiente. Esta implementación para el segundo bloque permite obtener búferes de fractal ya rellenos.

Ahora diremos unas palabras sobre el conjunto de puntos de fractal, el objeto CFractalSet. Debido a que he cambiado el método de representación del canal, también he tenido que modificar la clase CFractalSet. El método clave ha resultado CFractalSet::Calculate, que calcula los búferes del canal del indicador. El código se adjunta en el archivo CFractalPoint.mqh.



Ahora ya tenemos una base, el proveedor de señales del canal equidistante. El funcionamiento del indicador se muestra en el vídeo.


2. Estrategia básica

Así, propongo comenzar por algo sencillo, algo que se pueda perfeccionar y completar con POO. Que al menos exista una estrategia básica.

Esta estrategia analizará reglas comerciales bastante simples. La entrada en el mercado se efectúa a partir de los límites del canal. Cuando el precio toca el límite inferior, abrimos la compra, y cuando toca el superior, la venta. En la fig.1, el precio ha tocado el límite inferior, por eso el robot ha comprado un cierto volumen. Los niveles comerciales (stop-loss y take-profit) tienen un tamaño fijo y han sido colocados de forma automática. Si hay una posición abierta, ignoraremos las señales de entrada repetidas.


Fig.1 Señal de entrada


Quiero destacar también que la Biblioteca estándar ya ha crecido bastante. En ella ya hay muchas clases preparadas que podemos utilizar. Vamos a probar para comenzar a «unirnos» a la clase de señal CExpertSignal. De acuerdo con la documentación es una clase básica para la creación de generadores de señales comerciales.

A mi parecer, a esta clase le han puesto un nombre bastante exacto. No se trata de CTradeSignal ni de CSignal, sino precisamente de una clase de señales pensadas para el uso en el código del asesor CExpertSignal.

No voy a detenerme en su contenido. En el artículo «MQL5 Wizard: Cómo crear un módulo de señales de trading» se describen con detalle los métodos de la clase de señal.


2.1 Clase de señal CSignalEquidChannel 

Bien, la clase de señal derivada será así:

//+------------------------------------------------------------------+
//| Class CSignalEquidChannel                                        |
//| Purpose: Clase de señales comerciales en base al canal       |
//|          equidistante.                                                 |
//| Heredero de la clase CExpertSignal.                                    |
//+------------------------------------------------------------------+
class CSignalEquidChannel : public CExpertSignal
  {
protected:
   CiCustom          m_equi_chs;          // objeto-indicador "EquidistantChannels"   
   //--- parámetros ajustables
   int               m_prev_frac_num;     // de los fractales anteriores
   bool              m_to_plot_fracs;     // ¿representar fractales?
   int               m_bars_beside;       // barras la izquierda/derecha del fractal
   int               m_bars_between;      // barras intermedias
   ENUM_RELEVANT_EXTREMUM m_relevant_pnt; // punto actual
   int               m_line_width;        // grosor de la línea
   bool              m_to_log;            // ¿llevar un registro?
   double            m_pnt_in;            // tolerancia interna, pp
   double            m_pnt_out;           // tolerancia externa,pp
   bool              m_on_start;          // bandera de la señal al comenzar
   //--- de cálculo
   double            m_base_low_price;    // precio básico mínimo
   double            m_base_high_price;   // precio básico máximo
   double            m_upper_zone[2];     // zona superior: [0]-tolerancia interna, [1]-externa  
   double            m_lower_zone[2];     // zona inferior
   datetime          m_last_ch_time;      // hora de aparición del último canal
   //--- "pesos" de los modelos de mercado (0-100)
   int               m_pattern_0;         //  "contacto con el límite inferior del canal - buy, con el superior - sell"

   //--- === Methods === --- 
public:
   //--- constructor/destructor
   void              CSignalEquidChannel(void);
   void             ~CSignalEquidChannel(void){};
   //--- métodos de configuración de los parámetros ajustables
   void              PrevFracNum(int _prev_frac_num)   {m_prev_frac_num=_prev_frac_num;}
   void              ToPlotFracs(bool _to_plot)        {m_to_plot_fracs=_to_plot;}
   void              BarsBeside(int _bars_beside)      {m_bars_beside=_bars_beside;}
   void              BarsBetween(int _bars_between)    {m_bars_between=_bars_between;}
   void              RelevantPoint(ENUM_RELEVANT_EXTREMUM _pnt) {m_relevant_pnt=_pnt;}
   void              LineWidth(int _line_wid)          {m_line_width=_line_wid;}
   void              ToLog(bool _to_log)               {m_to_log=_to_log;}
   void              PointsOutside(double _out_pnt)    {m_pnt_out=_out_pnt;}
   void              PointsInside(double _in_pnt)      {m_pnt_in=_in_pnt;}
   void              SignalOnStart(bool _on_start)     {m_on_start=_on_start;}
   //--- métodos de ajuste de los "pesos" de los modelos de mercado
   void              Pattern_0(int _val) {m_pattern_0=_val;}
   //--- método de comprobación de los ajustes
   virtual bool      ValidationSettings(void);
   //--- método de creación del indicador y de las series temporales
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- métodos de comprobación, si los modelos de mercado están formados
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);
   virtual double    Direction(void);
   //---
protected:
   //--- método de inicialización del indicador
   bool              InitCustomIndicator(CIndicators *indicators);
   //- obteniendo los valores del límite superior del canal
   double            Upper(int ind) {return(m_equi_chs.GetData(2,ind));}
   //- obteniendo los valores del límite inferior del canal
   double            Lower(int ind) {return(m_equi_chs.GetData(3,ind));}
   //- obteniendo la bandera de aparición del canal
   double            NewChannel(int ind) {return(m_equi_chs.GetData(4,ind));}
  };
//+------------------------------------------------------------------+

Voy a matizar varias cosas.

En esta clase, el señalizador principal es la configuración en el canal equidistante. Y en la variante actual es el único. Por el momento no habrá ningún otro. Dentro de los componentes de la clase entra la clase para trabajar con el indicador técnico de tipo usuarioCiCustom

Como modelo de señal se usa el modelo básico: "contacto con el límite inferior del canal — buy, con el superior — sell". Puesto que el contacto se da con una precisión de hasta un punto, que, digamos, no es el caso más probable, se usa un búfer cuyos límites se pueden ajustar. El parámetro de tolerancia externa m_pnt_out define la magnitud de la salida permisible del precio fuera de los límites del canal, y el parámetro de tolerancia interna m_pnt_in, la distancia hasta la que el precio puede acercarse sin tocar el límite. La lógica es muy sencilla. Consideraremos que el precio ha tocado el límite del canal, si no lo ha alcanzado por un poco, o ha salido un poco de su límite. En la fig.2 se representa de forma esquemática un búfer. El precio, al incidir desde abajo en este, desarrolla un modelo junto con el límite.

Fig.2 Desarrollo del modelo de señal básico

Fig.2 Desarrollo del modelo de señal básico


El parámetro-matriz m_upper_zone[2] traza los límites del búfer superior, y m_lower_zone[2], los del inferior.

En el ejemplo, el nivel en $1,11552 ejerce de límite superior del canal (recta roja). El nivel en $1,11452 es responsable del extremo inferior del búfer, y $1,11702, del superior. Entonces, la magnitud de la tolerancia externa es de 150 pp, y de la interna, 100 pp. El precio se representa con una curva azul.

El parámetro m_on_start permite ignorar las señales del primer canal al iniciar el robot en el gráfico, si este canal ya se ha dibujado. Si la bandera ha sido reseteada, el robot funcionará solo en el siguiente canal, y en el actual no procesará las señales comerciales.

Los parámetros m_base_low_price y m_base_high_price guardan los valores de los precios máximos y mínimos de la barra actual. Así se calcula la barra cero si el modo comercial es por ticks, o bien la barra pasada, si solo se puede comerciar con la aparición de una nueva barra.

Ahora vamos a hablar un poco de los métodos. Aquí destacaremos que el desarrollador proporciona una amplia libertad de acción, y es que aproximadamente la mitad de los métodos son virtuales. Esto significa que podemos implementar el comportameinto de la clases-derivadas según nuestro propio juicio.

Voy a comenzar por el método Direction(), que valora de forma cuantitativa la dirección comercial potencial:

//+------------------------------------------------------------------+
//| Definiendo la dirección "ponderada"                            |
//+------------------------------------------------------------------+
double CSignalEquidChannel::Direction(void)
  {
   double result=0.;
//--- aparición de un nuevo canal
   datetime last_bar_time=this.Time(0);
   bool is_new_channel=(this.NewChannel(0)>0.);
//--- si ignoramos las señales del primer canal
   if(!m_on_start)
      //--- si el primer canal se representa normalmente al darse la inicialización
      if(m_prev_frac_num==3)
        {
         static datetime last_ch_time=0;
         //--- si ha aparecido un nuevo canal
         if(is_new_channel)
           {
            last_ch_time=last_bar_time;
            //--- si es el primer inicio
            if(m_last_ch_time==0)
               //--- recordar la hora de la barra donde ha aparecido el primer canal
               m_last_ch_time=last_ch_time;
           }
         //--- si las horas coinciden
         if(m_last_ch_time==last_ch_time)
            return 0.;
         else
         //--- resetear bandera
            m_on_start=true;
        }
//--- índice de la barra actual
   int actual_bar_idx=this.StartIndex();
//--- establecer los límites
   double upper_vals[2],lower_vals[2]; // [0]-barra que precede a la actual, [1]-barra actual
   ArrayInitialize(upper_vals,0.);
   ArrayInitialize(lower_vals,0.);
   for(int idx=ArraySize(upper_vals)-1,jdx=0;idx>=0;idx--,jdx++)
     {
      upper_vals[jdx]=this.Upper(actual_bar_idx+idx);
      lower_vals[jdx]=this.Lower(actual_bar_idx+idx);
      if((upper_vals[jdx]==0.) || (lower_vals[jdx]==0.))
         return 0.;
     }
//--- obtener precios
   double curr_high_pr,curr_low_pr;
   curr_high_pr=this.High(actual_bar_idx);
   curr_low_pr=this.Low(actual_bar_idx);
//--- si se han obtenido los precios
   if(curr_high_pr!=EMPTY_VALUE)
      if(curr_low_pr!=EMPTY_VALUE)
        {
         //--- recordar los precios
         m_base_low_price=curr_low_pr;
         m_base_high_price=curr_high_pr;
         //--- Determinar los precios para las zonas de búfer
         //--- zona superior: [0]-tolerancia interna, [1]-externa 
         this.m_upper_zone[0]=upper_vals[1]-m_pnt_in;
         this.m_upper_zone[1]=upper_vals[1]+m_pnt_out;
         //--- zona inferior: [0]-tolerancia interna, [1]-externa 
         this.m_lower_zone[0]=lower_vals[1]+m_pnt_in;
         this.m_lower_zone[1]=lower_vals[1]-m_pnt_out;
         //--- normalización
         for(int jdx=0;jdx<ArraySize(m_lower_zone);jdx++)
           {
            this.m_lower_zone[jdx]=m_symbol.NormalizePrice(m_lower_zone[jdx]);
            this.m_upper_zone[jdx]=m_symbol.NormalizePrice(m_upper_zone[jdx]);
           }
         //--- comprobar si las zonas coinciden
         if(this.m_upper_zone[0]<=this.m_lower_zone[0])
            return 0.;
         //--- resultado
         result=m_weight*(this.LongCondition()-this.ShortCondition());
        }
//---
   return result;
  }
//+------------------------------------------------------------------+

El primer bloque en el cuerpo del método es la comprobación sobre si es necesario ignorar el primer canal en el gráfico, si es que existe en general.

En el segundo bloque obtenemos los precios actuales y se determinan las zonas de búfer. También existe la comprobación de la coincidencia de estas zonas. Si el canal es demasiado estrecho o las zonas de búfer son demasiado anchas, esto significa que es probable que, desde un punto de vista puramente matemático, el precio pueda entrar en ambas zonas. Por eso debemos procesar también una situación así.

La línea meta se destaca en color azul. Aquí obtenemos una valoración cuantitativa de la dirección comercial, si la hay.

Ahora veremos el método LongCondition().

//+------------------------------------------------------------------+
//| Comprobando la condición de compra                                      |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
//--- si se ha establecido un precio mínimo
   if(m_base_low_price>0.)
      //--- si el precio mínimo está al nivel del límite inferior
      if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
        {
         if(IS_PATTERN_USAGE(0))
            result=m_pattern_0;
        }
//---
   return result;
  }
//+------------------------------------------------------------------+

Para la compra, comprobamos si el precio ha entrado en la zona de búfer inferior. Si ha entrado, entonces comprobamos si está permitido el empleo del modelo de mercado. Podrá lee con más detalle sobre la construcción del tipo "IS_PATTERN_USAGE(k)" en el artículo  «Generador de señales comerciales del indicador de usuario».

El método ShortCondition() funciona de forma análoga al descrito más arriba. Solo que aquí la atención se centra en la zona de búfer superior.

//+------------------------------------------------------------------+
//| Comprobando la condición de venta                                      |
//+------------------------------------------------------------------+
int CSignalEquidistantChannel::ShortCondition(void)
  {
   int result=0;
//--- si se ha establecido el precio máximo
   if(m_base_high_price>0.)
      //--- si el precio máximo está al nivel del límite superior
      if((m_base_high_price>=m_upper_zone[0]) && (m_base_high_price<=m_upper_zone[1]))
        {
         if(IS_PATTERN_USAGE(0))
            result=m_pattern_0;       
        }
//---
   return result;
  }
//+------------------------------------------------------------------+

En la clase se inicializa el indicador propio con la ayuda del método InitCustomIndicator():

//+------------------------------------------------------------------+
//| Inicialización de los indicadores propios                            |
//+------------------------------------------------------------------+
bool CSignalEquidChannel::InitCustomIndicator(CIndicators *indicators)
  {
//--- añadiendo un objeto a la colección
   if(!indicators.Add(GetPointer(m_equi_chs)))
     {
      PrintFormat(__FUNCTION__+": error adding object");
      return false;
     }
//--- estableciendo los parámetros del indicador
   MqlParam parameters[8];
   parameters[0].type=TYPE_STRING;
   parameters[0].string_value="EquidistantChannels.ex5";
   parameters[1].type=TYPE_INT;
   parameters[1].integer_value=m_prev_frac_num;   // 1) fractales anteriores
   parameters[2].type=TYPE_BOOL;
   parameters[2].integer_value=m_to_plot_fracs;   // 2) ¿representar fractales?
   parameters[3].type=TYPE_INT;
   parameters[3].integer_value=m_bars_beside;     // 3)  barras a la izquierda/derecha del fractal
   parameters[4].type=TYPE_INT;
   parameters[4].integer_value=m_bars_between;    // 4) barras intermedias
   parameters[5].type=TYPE_INT;
   parameters[5].integer_value=m_relevant_pnt;    // 5) punto actual
   parameters[6].type=TYPE_INT;
   parameters[6].integer_value=m_relevant_pnt;    // 6) grosor de la línea
   parameters[7].type=TYPE_BOOL;
   parameters[7].integer_value=m_to_log;          // 7) ¿llevar un registro?

//--- inicializando objeto
   if(!m_equi_chs.Create(m_symbol.Name(),_Period,IND_CUSTOM,8,parameters))
     {
      PrintFormat(__FUNCTION__+": error initializing object");
      return false;
     }
//--- número de búferes
   if(!m_equi_chs.NumBuffers(5))
      return false;
//--- ok
   return true;
  }
//+------------------------------------------------------------------+

En la matriz de parámetros hay que indicar en primer lugar el nombre de línea del indicador.

La clase tiene su propio método virtual ValidationSettings(). Este llama al método análogo del progenitor y comprueba si han sido correctamente establecidos los parámetros del indicador de canal. Todavía hay métodos de servicio que reciben los valores de los búferes correspondientes del indicador de usuario.

Por el momento es todo en lo que respecta a la clase de señal derivada.


2.2 Clase de la estrategia comercial CEquidChannelExpert

Para realizar la idea base implementada, tendremos que escribir una clase derivada de la clase estándar CExpert. En el paso actual su código deberá ser lo más compacto posible, puesto que en esencia, hay que cambiar solo el comportamiento del manejador principal, el método Processing(). Es un método virtual, lo que nos da la posibilidad de escribir cualquier estrategia.

//+------------------------------------------------------------------+
//| Clase CEquidChannelExpert.                                       |
//| Objetivo: Clase para el asesor que comercie según el canal equidistante. |
//| Descendiente de la clase CExpert.                                          |
//+------------------------------------------------------------------+
class CEquidChannelExpert : public CExpert
  {
   //--- === Data members === --- 
private:


   //--- === Methods === --- 
public:
   //--- constructor/destructor
   void              CEquidChannelExpert(void){};
   void             ~CEquidChannelExpert(void){};

protected:
   virtual bool      Processing(void);
  };
//+------------------------------------------------------------------+

Aquí tenemos al propio método:

//+------------------------------------------------------------------+
//| Módulo principal                                                   |
//+------------------------------------------------------------------+
bool CEquidChannelExpert::Processing(void)
  {
//--- cálculo de la dirección
   m_signal.SetDirection();
//--- check if open positions
   if(!this.SelectPosition())
     {
      //--- módulo de apertura de la posición
      if(this.CheckOpen())
         return true;
     }
//--- si no hay operaciones comerciales
   return false;
  }

Todo es muy sencillo. Primero el objeto de señal valora la posible dirección comercial, y después comprueba la presencia de posiciones abiertas. Si no las hay, entonces buscaremos la posibilidad de que se abran. Si hay una posición, salimos.

El código de la estrategia ha sido implementado en el archivo BaseChannelsTrader.mq5.




Mostramos en el vídeo un ejemplo del funcionamiento de la estrategia.



Fig.3 Resultados de la estrategia básica en 2013-2015
.

Se ha realizado una pasada en el simulador, en el marco temporal de una hora con el símbolo EURUSD. En el gráfico de balance se puede ver que la estrategia básica en ciertos segmentos ha funcionado con el "principio de la sierra": a una serie con pérdidas ha seguido una serie con ganancias. Los valores de los parámetros de usuario que se han usado durante la simulación se encuentran en el archivo base_signal.set. En ellos se indican también los parámetros del canal cuyos valores permanecerán inalterables en todas las versiones de la estrategia.

Aquí y más adelante se usa el modo de simulación "cada tick basado en ticks reales".

En general, hay 2 modos de mejorar los índices comerciales de la estrategia. El primero es la optimización, y consiste en elegir una combinación de valores de parámetros que maximice el beneficio, etc. El segundo es la búsqueda de los factores que influyen en el rendimiento del asesor. Si bien el primer método no está unido a un cambio en la lógica de la estrategia comercial, el segundo no podrá funcionar sin ella.

En el siguiente apartado corregiremos la estrategia básica y buscaremos los factores de rendimiento.


3. Factores de rendimiento

Unas cuantas palabras sobre la disposición. En mi opinión, es conveniente colocar todos los archivos que hacen única a la estrategia en una carpeta del proyecto. Por lo tanto, la implementación de la estrategia básica se encuentra en la subcarpeta Base (Fig.4), etc.


Fig.4 Ejemplo de la jerarquía de las carpetas de los proyectos de la estrategia de canales

A continuación, consideraremos cada factor como una nueva etapa para introducir cambios en los archivos fuente que forman el código del asesor.

3.1 Uso del trailing

Para empezar, propongo añadir a nuestra estrategia la posibilidad de trailing. Vamos a dejar que sea, por ejemplo, el objeto de la clase CTrailingFixedPips, que permite acompañar las posiciones abiertas a una "distancia" fija (en puntos). Además, el trailing actuará tanto sobre el precio del stop-loss, como sobre el precio del take-profit. Para que el profit no disponga de trailing, hay que indicar el valor cero para el parámetro correspondiente (InpProfitLevelPips).

Vamos a introducir en el código los siguientes cambios.

Añadiremos al archivo fuente del asesor ChannelsTrader1.mq5 un grupo de parámetros de usuario:

//---
sinput string Info_trailing="+===-- Трал --====+"; // +===-- Трал --====+
input int InpStopLevelPips=30;          // Nivel para StopLoss, pp
input int InpProfitLevelPips=50;        // Nivel para TakeProfit, pp

En el bloque de inicialización anotamos que estamos creando un objeto del tipo CTrailingFixedPips, lo incluimos en los componentes de la estrategia y establecemos los parámetros del trailing.

//--- objeto de trailing
   CTrailingFixedPips *trailing=new CTrailingFixedPips;
   if(trailing==NULL)
     {
      //--- error
      printf(__FUNCTION__+": error creating trailing");
      myChannelExpert.Deinit();
      return(INIT_FAILED);
     }
//--- añadiendo el objeto de trailing
   if(!myChannelExpert.InitTrailing(trailing))
     {
      //--- error
      PrintFormat(__FUNCTION__+": error initializing trailing");
      myChannelExpert.Deinit();
      return INIT_FAILED;
     }
//--- parámetros de trailing
   trailing.StopLevel(InpStopLevelPips);
   trailing.ProfitLevel(InpProfitLevelPips);

Dado que vamos a usar trailing, hay que modificar también el método principal CEquidChannelExpert::Processing() en el archivo EquidistantChannelExpert1.mqh.

//+------------------------------------------------------------------+
//| Módulo principal                                                   |
//+------------------------------------------------------------------+
bool CEquidChannelExpert::Processing(void)
  {
//--- cálculo de la dirección
   m_signal.SetDirection();
//--- si no hay posición
   if(!this.SelectPosition())
     {
      //--- módulo de apertura de la posición
      if(this.CheckOpen())
         return true;
     }
//--- si hay posición
   else
     {
      //--- comprobando si es posible modificar la posición
      if(this.CheckTrailingStop())
         return true;
     }
//--- si no hay operaciones comerciales
   return false;
  }

Eso es todo. Ya hemos añadido el trailing. Los archivos de la estrategia actualizada se encuentran en la subcarpeta aparte ChannelsTrader1.

Vamos a intentar comprobar si nuestra novedad influye en el rendimiento.

Bien, en el mismo segmento de la historia, con los mismos parámetros que en la estrategia básica, hemos hecho varias pasadas en el simulador en el modo de optimización. Han aumentado los parámetros de stop-loss y take-profit:

 Variable InicioSalto
Stop
Nivel para StopLoss, pp
0
10
100
Nivel para TakeProfit, pp
0
10
150

Los resultados de la optimización se encuentran en el archivo ReportOptimizer-signal1.xml. La mejor pasada se muestra en la fig.5, donde el Nivel para StopLoss = 0, y para TakeProfit = 150.


Рис.5 Resultados de la estrategia con el uso de trailing en 2013-2015.

Es fácil notar que la última figura recuerda a la Fig.3. De esta forma, se puede decir que en este diapasón de valores, el uso de trailing no ha posibilitado la mejora del resultado.


3.2 Tipo de canal

Existe la presuposición de que el tipo de canal influye en los resultados comerciales. La idea general es la siguiente: es mejor vender en el marco del canal descendente, y comprar en el ascendente. Si el canal es recto (no inclinado), entonces se puede comerciar a partir de ambos límites.

La enumeración ENUM_CHANNEL_TYPE determina el tipo de canal:

//+------------------------------------------------------------------+
//| Tipo de canal                                                       |
//+------------------------------------------------------------------+
enum ENUM_CHANNEL_TYPE
  {
   CHANNEL_TYPE_ASCENDING=0,  // ascendente
   CHANNEL_TYPE_DESCENDING=1, // descendente
   CHANNEL_TYPE_FLAT=2,       // recto
  };
//+------------------------------------------------------------------+

En el archivo fuente del asesor ChannelsTrader2.mq5, en el bloque de inicialización escribiremos que establecemos el parámetro de permiso para la búsqueda del tipo de canal.

//--- parámetros del filtro
   filter0.PointsInside(_Point*InpPipsInside);
   filter0.PointsOutside(_Point*InpPipsOutside);
   filter0.TypeTolerance(_Point*InpTypePips);
   filter0.PrevFracNum(InpPrevFracNum);
   ...

Este parámetro controla la velocidad de cambio del precio en puntos. Supongamos que es igual a 7 pp. Significa que si en cada barra el canal "crece" 6 pp, entonces no llega a considerarse ascendente. Entonces lo consideraremos recto (no inclinado).

Añadimos la búsqueda del tipo de canal al archivo fuente de la señal SignalEquidChannel2.mqh en el método Direction().

//--- si es un canal nuevo
   if(is_new_channel)
     {
      m_ch_type=CHANNEL_TYPE_FLAT;                // canal recto (no inclinado) 
      //--- si se indica el permiso para el tipo
      if(m_ch_type_tol!=EMPTY_VALUE)
        {
         //--- Tipo de canal
         //--- velocidad de cambio
         double pr_speed_pnt=m_symbol.NormalizePrice(upper_vals[1]-upper_vals[0]);
         //--- si la velicidad es suficiente
         if(MathAbs(pr_speed_pnt)>m_ch_type_tol)
           {
            if(pr_speed_pnt>0.)
               m_ch_type=CHANNEL_TYPE_ASCENDING;  // canal ascendente
            else
               m_ch_type=CHANNEL_TYPE_DESCENDING; // canal descendente             
           }
        }
     }

Al principio, el canal se considera recto, ni crece, ni disminuye. Si no se ha establecido el valor del parámetro de tolerancia para la búsqueda del tipo de canal, entonces el cálculo de la velocidad de cambio no será necesario.

 La condición de compra será incluir una comprobación de que el canal no sea descendente.

//+------------------------------------------------------------------+
//| Comprobando la condición de compra                                      |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
//--- si se ha establecido un precio mínimo
   if(m_base_low_price>0.)
      //--- si el canal no es descendente
      if(m_ch_type!=CHANNEL_TYPE_DESCENDING)
         //--- si el precio mínimo está al nivel del límite inferior
         if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
           {
            if(IS_PATTERN_USAGE(0))
               result=m_pattern_0;
           }
//---
   return result;
  }
//+------------------------------------------------------------------+

Una comprobación análoga se realiza en la condición de venta, que el canal no sea ascendente.

El método principal CEquidChannelExpert::Processing() en el archivo EquidistantChannelExpert2.mqh será igual que en la versión básica, puesto que excluimos el trailing.

Comprobamos el rendimiento de este factor. Solo optimizamos un parámetro.

 Variable InicioSalto
Stop
Tolerancia para el tipo, pp
0
5
150

Los resultados de la optimización se encuentran en el archivo ReportOptimizer-signal2.xml. La mejor pasada se muestra en la Fig.6.


Fig.6 Resultados de la estrategia con el uso del tipo de canal en 2013-2015.


Es fácil ver que los resultados de la estrategia son ligeramente mejores que los resultados de la estrategia básica. Resulta que teniendo establecidos los valores básicos de los parámetros, un factor como el tipo de canal afecta al resultado final. 


3.3 Anchura del canal

Me parece que la anchura del canal puede influir en el tipo de la propia estrategia. Si el canal resulta estrecho, entonces al darse la ruptura de su límite (superior o inferior) se puede jugar en la dirección de la ruptura, y no al contrario. Entonces obtendremos una estrategia de ruptura. Si el canal resulta ancho, se puede comerciar a partir de sus límites. Esto sería una estrategia basada en el rebote. La estrategia actual es precisamente de ese tipo, el comercio parte de los límites del canal.

Es obvio que necesitamos un criterio para determinar si el canal es estrecho o ancho. Para no caer en extremismos, propondremos algo intermedio, para no considerar el canal estudiado ni estrecho, ni ancho. Al final, necesitaremos 2 criterios:

  1. una anchura suficiente del canal estrecho;
  2. una anchura suficiente del canal ancho;

Si el canal no es ni el primero, ni el segundo, podemos no entrar en el mercado.

Fig.5 Amplitud del canal, esquema

Fig.7 Amplitud del canal, esquema

Destacaremos que hay un problema geométrico a la hora de definir la amplitud del canal. Y es que en el gráfico los ejes se miden en magnitudes diferentes. Así, es fácil calcular la longitud de los segmentos AB y CD. Pero hay un problema con el cálculo del segmento CE (Fig.7).

Puede que hayamos elegido el método de normalización más discutible y no más preciso, pero al menos es sencillo. La fórmula es la siguiente:

longitud del segmento CE ≃ longitud del segmento CD / (1.0 + velocidad del canal)

Fijamos la amplitud del canal con la ayuda de la enumeración ENUM_CHANNEL_WIDTH_TYPE:

//+------------------------------------------------------------------+
//| Amplitud del canal                                                    |
//+------------------------------------------------------------------+
enum ENUM_CHANNEL_WIDTH_TYPE
  {
   CHANNEL_WIDTH_NARROW=0,   // estrecho
   CHANNEL_WIDTH_MID=1,      // medio
   CHANNEL_WIDTH_BROAD=2,    // ancho
  };

En el archivo fuente del asesor ChannelsTrader3.mq5 añadimos al grupo de parámetros de usuario "Canales" los criterios de amplitud del canal:

//---
sinput string Info_channels="+===-- Canales --====+"; // +===-- Canales --====+
input int InpPipsInside=100;            // Tolerancia interna, pp
input int InpPipsOutside=150;           // Tolerancia externa, pp
input int InpNarrowPips=250;            // Canal estrecho, pp
input int InpBroadPips=1200;            // Canal ancho, pp
...

Si el criterio del canal estrecho tiene un valor que supere el valor análogo del ancho, entonces habrá un error de inicialización:

//--- parámetros del filtro
   filter0.PointsInside(_Point*InpPipsInside);
   filter0.PointsOutside(_Point*InpPipsOutside);
   if(InpNarrowPips>=InpBroadPips)
     {
      PrintFormat(__FUNCTION__+": error specifying narrow and broad values");
      return INIT_FAILED;
     }
   filter0.NarrowTolerance(_Point*InpNarrowPips);
   filter0.BroadTolerance(_Point*InpBroadPips);

En el código, el momento en el que se determina la anchura del canal se muestra en el cuerpo del método Direction().

//--- Anchura del canal 
   m_ch_width=CHANNEL_WIDTH_MID;               // media
   double ch_width_pnt=((upper_vals[1]-lower_vals[1])/(1.0+pr_speed_pnt));
//--- si se indica el criterio del estrecho
   if(m_ch_narrow_tol!=EMPTY_VALUE)
      if(ch_width_pnt<=m_ch_narrow_tol)
         m_ch_width=CHANNEL_WIDTH_NARROW;      // estrecha      
//--- si se indica el criterio del ancho
   if(m_ch_narrow_tol!=EMPTY_VALUE)
      if(ch_width_pnt>=m_ch_broad_tol)
         m_ch_width=CHANNEL_WIDTH_BROAD;       // ancha 

En primera instancia, consideraremos la anchura del canal como media. Luego comprobamos si el canal es estrecho o ancho.

Asimismo, necesitamos cambiar también los métodos para determinar la dirección comercial. Así, la condición de compra tendrá ahora el aspecto siguiente:

//+------------------------------------------------------------------+
//| Comprobando la condición de compra                                      |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
//--- si el canal es estrecho, jugamos con la ruptura del límite superior
   if(m_ch_width==CHANNEL_WIDTH_NARROW)
     {
      //--- si se ha establecido el precio máximo
      if(m_base_high_price>0.)
         //--- si el precio máximo está al nivel del límite superior
         if(m_base_high_price>=m_upper_zone[1])
           {
            if(IS_PATTERN_USAGE(0))
               result=m_pattern_0;
           }
     }
//--- si el canal es ancho, jugamos con el rebote del límite inferior
   else if(m_ch_width==CHANNEL_WIDTH_BROAD)
     {
      //--- si se ha establecido un precio mínimo
      if(m_base_low_price>0.)
         //--- si el precio mínimo está al nivel del límite inferior
         if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
           {
            if(IS_PATTERN_USAGE(0))
               result=m_pattern_0;
           }
     }
//---
   return result;
  }
//+------------------------------------------------------------------+

El método consta de dos bloques. En el primero se comprueba la posibilidad de jugar con la ruptura en el marco de un canal estrecho. Notemos que en la variante actual, se considerará ruptura cuando el precio alcance la parte superior de la zona de búfer superior. En el segundo bloque ya buscamos si el precio ha entrado en la zona de búfer inferior, para que entre en juego la estrategia de rebote.

El método de comprobación de la posibilidad de venta ShortCondition() ha sido creado por analogía.

El método principal CEquidChannelExpert::Processing() en el archivo EquidistantChannelExpert3.mqh no se modifica.

Para la optimización hay 2 parámetros.

 Variable InicioSalto
Stop
Canal estrecho, pp
100
20
250
Canal ancho, pp
350
50
1250

Los resultados de optimización se encuentran en el archivo ReportOptimizer-signal3.xml. La mejor pasada se muestra en la Fig.8.


Fig.8 Resultados de la estrategia teniendo en cuenta la anchura del canal en 2013-2015.


De todos los factores descritos aquí, este ha resultado el más influyente. La curva de balance ya tiene una dirección más definida.


3.4 Niveles frontera de stop-loss y take-profit

Si existen de forma anticipada unos ciertos objetivos comerciales expresados en valores de niveles de stop-loss y Take-profit, entonces merece la pena tener la posibilidad de adaptar estos objetivos a las condiciones de la estrategia actual. En pocas palabras, ya que disponemos de un canal que tiene su propio camino en el gráfico con un cierto ángulo, deberemos desplazar nuestros niveles de stop-loss y take-profit ligados a los límites del canal.

He añadido otro par de modelos, para mayor comodidad. Ahora tienen el aspecto siguiente:

//--- "pesos" de los modelos de mercado (0-100)
   int               m_pattern_0;         //  Modelo "rebote contra el límite del canal"
   int               m_pattern_1;         //  Modelo "ruptura del límite del canal"
   int               m_pattern_2;         //  Modelo "nuevo canal"

En las versiones anteriores solo había uno, y era responsable del contacto del precio con cualquier límite del canal. Ahora diferenciaremos entre el modelo de rebote y el de ruptura. Ha aparecido un tercer modelo, el modelo del nuevo canal. Sirve para aquellos casos en los que hay un nuevo canal y hay una posición abierta en el anterior canal. Si el modelo se ha activado, cerraremos esta posición.

Las condiciones de compra tienen este aspecto:

//+------------------------------------------------------------------+
//| Comprobando la condición de compra                                      |
//+------------------------------------------------------------------+
int CSignalEquidChannel::LongCondition(void)
  {
   int result=0;
   bool is_position=PositionSelect(m_symbol.Name());
//--- si el canal es estrecho, jugamos con la ruptura del límite superior
   if(m_ch_width_type==CHANNEL_WIDTH_NARROW)
     {
      //--- si se ha establecido el precio máximo
      if(m_base_high_price>0.)
         //--- si el precio máximo está al nivel del límite superior
         if(m_base_high_price>=m_upper_zone[1])
           {
            if(IS_PATTERN_USAGE(1))
              {
               result=m_pattern_1;
               //--- si no hay posición
               if(!is_position)
                  //--- en el Registro
                  if(m_to_log)
                    {
                     Print("\nSe ha activado el Modelo \"ruptura del límite del canal\" para la compra.");
                     PrintFormat("High-цена: %0."+IntegerToString(m_symbol.Digits())+"f",m_base_high_price);
                     PrintFormat("Триггер-цена: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[1]);
                    }
              }
           }
     }
//--- o si el canal es ancho o medio, jugamos con el rebote contra el límite inferior
   else
     {
      //--- si se ha establecido un precio mínimo
      if(m_base_low_price>0.)
         //--- si el precio mínimo está al nivel del límite inferior
         if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1]))
           {
            if(IS_PATTERN_USAGE(0))
              {
               result=m_pattern_0;
               //--- si no hay posición
               if(!is_position)
                  //--- en el Registro
                  if(m_to_log)
                    {
                     Print("\nSe ha activado el Modelo \"rebote contra el límite del canal\" para la compra.");
                     PrintFormat("Low-цена: %0."+IntegerToString(m_symbol.Digits())+"f",m_base_low_price);
                     PrintFormat("Зона вверх: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[0]);
                     PrintFormat("Зона низ: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[1]);
                    }
              }
           }
     }
//---
   return result;
  }
//+------------------------------------------------------------------+

Asimismo, ha aparecido la comprobación de la condición de cierre:

//+------------------------------------------------------------------+
//| Comprobación de la condición de cierre de la compra                                |
//+------------------------------------------------------------------+
bool CSignalEquidChannel::CheckCloseLong(double &price) const
  {
   bool to_close_long=true;
   int result=0;
   if(IS_PATTERN_USAGE(2))
      result=m_pattern_2;
   if(result>=m_threshold_close)
     {
      if(m_is_new_channel)
         //--- si cerramos la compra
         if(to_close_long)
           {
            price=NormalizeDouble(m_symbol.Bid(),m_symbol.Digits());
            //--- en el Registro
            if(m_to_log)
              {
               Print("\nSe ha activado el Modelo \"nuevo canal\" para el cierre de compra.");
               PrintFormat("Цена закрытия: %0."+IntegerToString(m_symbol.Digits())+"f",price);
              }
           }
     }
//---
   return to_close_long;
  }
//+------------------------------------------------------------------+
Para la posición corta, la condición de cierre será idéntica.


Ahora, unas cuantas palabras sobre el trailing. Para él se ha escrito su propia clase CTrailingEquidChannel, cuyo progenitor ha sido la clase estándar CExpertTrailing.

//+------------------------------------------------------------------+
//| Class CTrailingEquidChannel.                                     |
//| Purpose: Class of trailing stops based on Equidistant Channel.   |
//|              Derives from class CExpertTrailing.                 |
//+------------------------------------------------------------------+
class CTrailingEquidChannel : public CExpertTrailing
  {
protected:
   double            m_sl_distance;       // distancia para el stop
   double            m_tp_distance;       // distancia para el profit
   double            m_upper_val;         // límite superior
   double            m_lower_val;         // límite inferior
   ENUM_CHANNEL_WIDTH_TYPE m_ch_wid_type; // tipo de canal por anchura
   //---
public:
   void              CTrailingEquidChannel(void);
   void             ~CTrailingEquidChannel(void){};
   //--- methods of initialization of protected data
   void              SetTradeLevels(double _sl_distance,double _tp_distance);
   //---
   virtual bool      CheckTrailingStopLong(CPositionInfo *position,double &sl,double &tp);
   virtual bool      CheckTrailingStopShort(CPositionInfo *position,double &sl,double &tp);
   //---
   bool              RefreshData(const CSignalEquidChannel *_ptr_ch_signal);
  };
//+------------------------------------------------------------------+

En color rojo se destaca el método que recibe la información de la señal de canal.

Los métodos de comprobación de la posibilidad del trailing de una posición corta o larga de un progenitor han sido redefinidos mediante un polimorfismo, el principio básico de la POO.

Para que la clase de trailing pueda conseguir referencias temporales y de precio del canal actual, ha sido necesario crear una conexión con la clase de señal CSignalEquidChannel. Ha sido implementada por un puntero de constante entre los componentes de la clase CEquidChannelExpert. Este enfoque permite recibir toda la información necesaria de la señal, sin poner en peligro de cambio los valores de los parámetros de la propia señal.

//+------------------------------------------------------------------+
//| Clase CEquidChannelExpert.                                       |
//| Objetivo: Clase para el asesor que comercie según el canal equidistante. |
//| Descendiente de la clase CExpert.                                          |
//+------------------------------------------------------------------+
class CEquidChannelExpert : public CExpert
  {
   //--- === Data members === --- 
private:
   const CSignalEquidChannel *m_ptr_ch_signal;

   //--- === Methods === --- 
public:
   //--- constructor/destructor
   void              CEquidChannelExpert(void);
   void             ~CEquidChannelExpert(void);
   //--- puntero al objeto de la señal de los canales
   void              EquidChannelSignal(const CSignalEquidChannel *_ptr_ch_signal){m_ptr_ch_signal=_ptr_ch_signal;};
   const CSignalEquidChannel *EquidChannelSignal(void) const {return m_ptr_ch_signal;};

protected:
   virtual bool      Processing(void);
   //--- trade close positions check
   virtual bool      CheckClose(void);
   virtual bool      CheckCloseLong(void);
   virtual bool      CheckCloseShort(void);
   //--- trailing stop check
   virtual bool      CheckTrailingStop(void);
   virtual bool      CheckTrailingStopLong(void);
   virtual bool      CheckTrailingStopShort(void);
  };
//+------------------------------------------------------------------+

En la clase del asesor también se han redefinido los métodos responsables del cierre y el trailing.

El método principal CEquidChannelExpert::Processing() en el archivo EquidistantChannelExpert4.mqh tiene el aspecto siguiente:

//+------------------------------------------------------------------+
//| Módulo principal                                                   |
//+------------------------------------------------------------------+
bool CEquidChannelExpert::Processing(void)
  {
//--- cálculo de la dirección
   m_signal.SetDirection();
//--- si no hay posición
   if(!this.SelectPosition())
     {
      //--- módulo de apertura de la posición
      if(this.CheckOpen())
         return true;
     }
//--- si hay posición
   else
     {
      if(!this.CheckClose())
        {
         //--- comprobando si es posible modificar la posición
         if(this.CheckTrailingStop())
            return true;
         //---
         return false;
        }
      else
        {
         return true;
        }
     }
//--- si no hay operaciones comerciales
   return false;
  }
//+------------------------------------------------------------------+
Vamos a optimizar estos parámetros:
 Variable InicioSalto
Stop
Stop-loss, point
25
5
75
Take-profit, point50
5
200

Los resultados de la optimización se encuentran en el archivo ReportOptimizer-signal4.xml. La mejor pasada se muestra en la Fig.9.


Fig.9 Resultados de la estrategia teniendo en cuenta los niveles frontera en 2013-2015.


Es obvio que este factor - los niveles de precio frontera - no han posibilitado una mejora del rendimiento.


Conclusión

En este artículo he tratado de imaginar el desarrollo e implementación de una clase-señalizadora basada en los canales móviles. A cada versión de la señal le sigue una estrategia comercial con los resultados de la simulación.

Hay que destacar que en todas partes se han usado valores fijos de los ajustes del canal equidistante. Por eso, las conclusiones sobre el rendimiento de este o aquel factor son justas solo para los valores indicados.

Quedan otras posibilidades de mejora de los índices de rendimiento. En el marco de este artículo se ha mostrado parte de la búsqueda de estos índices.


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

Archivos adjuntos |
reports.zip (18.06 KB)
base_signal.set (1.54 KB)
channelstrader.zip (253.85 KB)
Red Neuronal: EA autooptimizable Red Neuronal: EA autooptimizable
¿Podríamos diseñar un EA que periódicamente, según ordenara su código, autooptimizara los criterios de apertura o cierre de posición?.¿Qué pasaría si implementamos en el EA una red neuronal (perceptrón multicapa) que sea el módulo que analice el historial y evalúe la estrategia?. Podríamos decirle al código: "optimiza cada mes (cada semana, cada día o cada hora) la red neuronal y continúa tu trabajo". ¡De esta forma, tendríamos un EA autooptimizable!
Asesor experto multiplataforma: reutilizando los componentes de la Biblioteca Estándar MQL5 Asesor experto multiplataforma: reutilizando los componentes de la Biblioteca Estándar MQL5
En la Biblioteca Estándar MQL5 hay ciertos componentes que pueden resultar útiles en las versiones de los asesores expertos multiplataforma para MQL4. En esta artículo analizaremos los métodos de creación de ciertos componentes de la Biblioteca Estándar MQL5 que son compatibles con el compilador MQL4.
Trabajando con cestas de parejas de divisas en el mercado fórex Trabajando con cestas de parejas de divisas en el mercado fórex
En el artículo se analizan cuestiones relacionadas con la división en grupos de las parejas de divisas, las cestas; también sobre cómo obtener datos sobre el estado de estas cestas (por ejemplo, sobrecompra o sobreventa); qué indicadores pueden proporcionar estos datos; y al fin, sobre cómo se puede aplicar la información obtenida en el trading práctico.
Cómo desarrollar y depurar rápidamente cualquier estrategia de scalping en MetaTrader 5 Cómo desarrollar y depurar rápidamente cualquier estrategia de scalping en MetaTrader 5
Los sistemas automáticos de scalping se consideran por derecho propio la cima del trading automático, y precisamente por ello, son a la vez los más complejos a la hora de escribir el código. En este artículo vamos a mostrar cómo se pueden construir estrategias basadas en el análisis de ticks entrantes con la ayuda de los recursos de depuración incorporados y de la simulación visual. Para desarrollar las reglas de entrada y salida con frecuencia se necesitan años de comercio manual. Pero con la ayuda de MetaTrader 5 usted podrá comprobar cualquier estrategia similar en la historia real.