English Русский 中文 Deutsch 日本語 Português
preview
Cómo crear cualquier tipo de Trailing Stop y conectarlo a un asesor experto

Cómo crear cualquier tipo de Trailing Stop y conectarlo a un asesor experto

MetaTrader 5Ejemplos | 14 noviembre 2024, 09:28
503 0
Artyom Trishkin
Artyom Trishkin

Contenido


Introducción

Continuando con el tema del trailing stop iniciado en el último artículo, hoy analizaremos las clases de trailing necesarias para crear cómodamente varios algoritmos de trailing de posiciones Stop Loss. Basándonos en las clases creadas, podremos implementar cualquier algoritmo de desplazamiento de stops: según la separación del stop respecto al precio actual, según los indicadores, según los valores especificados de los niveles Stop Loss, etc. Después de leer este artículo, seremos capaces de crear y conectar cualquier algoritmo de desplazamiento de stop de la posición a cualquier EA. Al mismo tiempo, la conexión y el uso del propio trailing serán cómodos y fáciles de entender.

Vamos a considerar brevemente el algoritmo de funcionamiento del trailing stop. Nos atendremos a la condición de que para cada trailing se puedan utilizar tres condiciones para su funcionamiento:

  • trailing start - número de puntos de beneficio de la posición en el que se activará el trailing stop;
  • trailing step - número de pips que el precio deberá pasar en la dirección del beneficio de la posición para el siguiente desplazamiento de la posición Stop Loss;
  • distancia de trailing - distancia desde el precio actual a la que se mantendrá el Stop Loss.

Estos tres parámetros pueden aplicarse a cualquier trailing. Cualquiera de estos parámetros puede o no estar presente en los ajustes de trailing en caso de que no sean necesarios o sean sustituidos por algún valor en el algoritmo de trailing. Un ejemplo de sustitución del parámetro "distancia de trailing" sería el valor del indicador al que se fija el Stop Loss de la posición. En este caso, si este parámetro está activado, el stop se fijará no en el precio establecido por el indicador, sino a una distancia en pips del precio indicado.

En general, los tres parámetros anteriores son los más usados en los distintos trailings, y los tendremos en cuenta a la hora de crear las clases de trailing.

Al desplazar una posición Stop Loss al precio requerido, deberemos realizar comprobaciones:

  • El precio StopLoss no deberá estar más cerca del precio actual que el valor de StopLevel del símbolo (SYMBOL_TRADE_STOPS_LEVEL, el margen mínimo en pips desde el precio de cierre actual para establecer órdenes Stop);
  • El precio StopLoss no deberá ser igual al precio ya fijado y deberá encontrarse por encima del nivel actual de Stop Loss para una posición larga y por debajo para una posición corta;
  • si los parámetros anteriores se utilizan en el algoritmo de trailing, las condiciones establecidas por estos parámetros deberán comprobarse adicionalmente.

Son comprobaciones básicas. Cualquier algoritmo de trailing funciona de la misma forma: se introduce el precio necesario para fijar un stop, se comprueban todas las restricciones necesarias y, si se superan todas las comprobaciones, el stop de la posición se desplazará al nivel especificado.

Así es como puede funcionar un trailing simple:

  1. en el parámetro "trailing start" se especifica el beneficio en pips al alcanzar el cual será necesario iniciar el trailing, o cero, si no se utiliza este parámetro;
  2. en el parámetro "trailing step" se especifica el número de puntos de beneficio después de los cuales el stop deberá situarse detrás del precio, o cero si no se usa este parámetro;
  3. en el parámetro "trailing distance" se establece la distancia desde el precio actual hasta el stop de la posición en la que deberá mantenerse el stop, o cero si no se utiliza este parámetro;
  4. adicionalmente podemos establecer el símbolo y el número mágico de las posiciones a monitorear, o bien NULL y -1 respectivamente, si deseamos rastrear las posiciones de todos los símbolos y con cualquier número mágico.

Cuando el beneficio de una posición alcanza el número indicado de pips, se activará el trailing y se establecerá una posición de stop a la distancia especificada del precio actual. A continuación, después de que el precio haya recorrido el número especificado de puntos hacia el lado del beneficio de la posición (paso de trailing), la posición de stop se desplazará nuevamente detrás del precio para mantener la distancia especificada con respecto a él. Y así continuamente hasta que el precio vaya en contra de la posición. En este caso, el stop se mantendrá en el nivel ya fijado. Una vez que el precio lo alcance, la posición se cerrará con beneficios mediante un Stop Loss. Por lo tanto, el trailing stop nos permitirá mantener el beneficio gracias al cierre de la posición cuando el precio se invierta, dejando al mismo tiempo que el precio "vague" dentro del rango establecido para el stop del precio, y tirando paso a paso hacia arriba de la parada después del precio, si se mueve en la dirección de la posición.

En lugar del parámetro "trailing distance", podemos especificar un valor de precio obtenido, por ejemplo, de un indicador. En este caso obtendremos un trailing en el indicador. Podemos transmitir el valor de una de las velas anteriores (High, Low, por ejemplo), entonces lograremos una búsqueda según los precios de la barra, etc. Pero el trailing básico, su algoritmo, permanecerá inalterado.

Por ello, para crear cualquier algoritmo de trailing stop, primero deberemos crear un trailing simple que nos permita transmitir los precios deseados para las posiciones de Stop Loss, y también desplace las posiciones de Stop Loss a ese nivel con todas las comprobaciones necesarias.

En el último artículo ya vimos las funciones de inclusión en las que había un trailing simple y trailings sobre distintos indicadores. Este enfoque permite conectar un trailing stop al EA para rastrear las posiciones del símbolo actual. Pero tiene algunas limitaciones: para cada trailing del indicador, deberemos crear un indicador en el propio EA y enviar su manejador a la función de trailing. Las propias funciones de trailing solo podrán trabajar con las posiciones del símbolo actual.

Si utilizamos clases de trailing, podremos crear muchas instancias de una clase de trailing con diferentes configuraciones, y luego todos los trailings creados de esta manera podrán trabajar simultáneamente en un asesor experto según un determinado algoritmo establecido por el programador. Es decir, para cada conjunto de parámetros prácticamente "con un par de líneas de código" creará su propio trailing que funcionará según su propio algoritmo. Cada uno de los trails creados podrá ejecutarse en el asesor experto de acuerdo con ciertas condiciones, creando complejos algoritmos para los trailing de posiciones de Stop Loss.

Incluso si crean dos trailings idénticos, pero para símbolos diferentes, se creará un trailing distinto para cada símbolo, que trabajará sobre los datos del símbolo que se ha especificado al crear el trailing. Esto simplificará enormemente el uso de las trailings en nuestros programas: solo tendremos que crear un trailing con los parámetros necesarios y llamarlo en los manejadores del EA. Para ser justos, debemos señalar que este enfoque podría no resultar óptimo en algunos algoritmos, ya que para cada tipo de trailing se ejecutará una búsqueda diferente de las posiciones existentes en el terminal. Si creamos un algoritmo realmente universal que use la misma lista de posiciones terminales para cada trailing, podría resultar que el coste de su diseño y desarrollo no sea comparable a su demanda. Y además, esto queda fuera del alcance de este artículo.

Por lo tanto, primero tendremos que escribir un marco: una simple clase de trailing stop que desplace el Stop Loss de las posiciones a un precio especificado con todas las comprobaciones necesarias.

En el artículo de la semana pasada, este simple trailing básico se llamaba TrailingStopByValue(). Aquí lo llamaremos SimpleTrailing, y podremos utilizarlo plenamente en nuestros programas.

Al igual que en el artículo anterior, todas las clases de monitoreo se ubicarán en un archivo. Este archivo se introducirá en el EA y se utilizará. Normalmente los archivos de inclusión se almacenan en la carpeta \MQL5\Include\ o en una subcarpeta de este directorio.


Clase básica final

En la carpeta del terminal \MQL5\Include\, crearemos una nueva subcarpeta Trailings\ y en ella un nuevo archivo de clase con el nombre Trailings.mqh:



El nombre de la clase deberá ser CSimpleTrailing, el nombre del archivo creado, Trailings.mqh, y la clase básica deberá ser una clase de objeto básico CObject de la biblioteca estándar:


Al finalizar el wizard MQL, obtendremos esta plantilla de clase en la carpeta \MQL5\Include\Trailings\Trailings.mqh:

//+------------------------------------------------------------------+
//|                                                    Trailings.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
class CSimpleTrailing : public CObject
  {
private:

public:
                     CSimpleTrailing();
                    ~CSimpleTrailing();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSimpleTrailing::CSimpleTrailing()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CSimpleTrailing::~CSimpleTrailing()
  {
  }
//+------------------------------------------------------------------+

Luego conectaremos el archivo de objetos básicos de la biblioteca estándar al archivo creado:

//+------------------------------------------------------------------+
//|                                                    Trailings.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Object.mqh>

//+------------------------------------------------------------------+
//| Class of the position StopLoss simple trailing                   |
//+------------------------------------------------------------------+
class CSimpleTrailing : public CObject

¿Por qué debemos heredar todas las clases de trailing creadas del objeto básico de la biblioteca estándar?

Esto nos permitirá crear fácilmente colecciones de trailings a partir de estas clases: ponerlas en listas, buscar en las listas los objetos deseados, etc. Es decir, si solo queremos usar futuros objetos de clase como instancias en archivos del EA, no será necesario heredar de CObject. Pero si queremos convertir estas clases en colecciones completas y utilizar todas las características de la Biblioteca Estándar, entonces estas clases deberán ser heredadas de la clase básica CObject:

La clase CObject es la clase básica para construir la biblioteca estándar MQL5 y proporciona a todos sus descendientes la posibilidad de ser un elemento de una lista vinculada.
Además, se definirán una serie de métodos virtuales para su posterior implementación en clases descendientes.

En la sección privada de la clase, declararemos tres métodos:

class CSimpleTrailing : public CObject
  {
private:
//--- check the criteria for modifying the StopLoss position and return the flag
   bool              CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick);

//--- modify StopLoss of a position by its ticket
   bool              ModifySL(const ulong ticket, const double stop_loss);

//--- return StopLevel in points
   int               StopLevel(void);

protected: 

El método CheckCriterion() retornará una bandera que indicará si se han superado con éxito (o no) todas las comprobaciones necesarias para modificar la posición StopLoss.

El método ModifySL() modificará el Stop Loss de la posición según el valor especificado.

El método StopLevel() retornará el valor de la propiedad StopLevel del símbolo.

Todos estos métodos eran funciones separadas en el último artículo, pero ahora funcionarán como parte de la clase básica de trailing, de la que heredaremos otras clases de otras clases de trailing.

En la sección protegida de la clase, declararemos todas las variables necesarias para que la clase funcione:

protected: 
   string            m_symbol;                        // trading symbol
   long              m_magic;                         // EA ID
   double            m_point;                         // Symbol Point
   int               m_digits;                        // Symbol digits
   int               m_offset;                        // stop distance from price
   int               m_trail_start;                   // profit in points for launching trailing
   uint              m_trail_step;                    // trailing step
   uint              m_spread_mlt;                    // spread multiplier for returning StopLevel value
   bool              m_active;                        // trailing activity flag

//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:

De todas las variables declaradas aquí, solo el "multiplicador de spread" requiere una explicación. El valor StopLevel del símbolo será la distancia al precio actual, más cerca de la cual no se podrá colocar un Stop Loss. Y, si el valor de StopLevel está a cero en el servidor, no significará que no haya StopLevel. Esto significará que el nivel de stop es flotante y normalmente será igual a dos spreads. A veces tres. Todo dependerá de la configuración del servidor. Por lo tanto, aquí podremos fijar este multiplicador de forma independiente. Si utilizamos un spread doble para el valor StopLevel, el valor de la variable m_spread_mlt se establecerá en dos (valor predeterminado). Si es triple, será tres, etc.

El método virtual GetStopLossValue() calculará y retornará el precio StopLoss de una posición. En los distintos tipos de trailing, el nivel de Stop Loss en el que colocar una posición de stop se calculará de forma diferente. Precisamente por esto dicho método se declarará virtual, y deberá ser redefinido en cada clase de trailing heredada si tienen un cálculo de Stop Loss diferente al que se escribirá en este método de esa clase.

En la sección pública de la clase escribiremos métodos para establecer y retornar los valores a las variables declaradas en la sección protegida de la clase, así como constructores y destructores de la clase:

public:
//--- set trailing parameters
   void              SetSymbol(const string symbol)
                       {
                        this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                        this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                        this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                       }
   void              SetMagicNumber(const long magic)       { this.m_magic = magic;       }
   void              SetStopLossOffset(const int offset)    { this.m_offset = offset;     }
   void              SetTrailingStart(const int start)      { this.m_trail_start = start; }
   void              SetTrailingStep(const uint step)       { this.m_trail_step = step;   }
   void              SetSpreadMultiplier(const uint value)  { this.m_spread_mlt = value;  }
   void              SetActive(const bool flag)             { this.m_active = flag;       }

//--- return trailing parameters
   string            Symbol(void)                     const { return this.m_symbol;       }
   long              MagicNumber(void)                const { return this.m_magic;        }
   int               StopLossOffset(void)             const { return this.m_offset;       }
   int               TrailingStart(void)              const { return this.m_trail_start;  }
   uint              TrailingStep(void)               const { return this.m_trail_step;   }
   uint              SpreadMultiplier(void)           const { return this.m_spread_mlt;   }
   bool              IsActive(void)                   const { return this.m_active;       }

//--- launch trailing with StopLoss offset from the price
   bool              Run(void);

//--- constructors
                     CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()),
                                         m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {}
                     CSimpleTrailing(const string symbol, const long magic,
                                     const int trailing_start, const uint trailing_step, const int offset);
//--- destructor
                    ~CSimpleTrailing() {}
  };

La clase tendrá dos constructores:

  • El primer constructor será el predeterminado, donde el símbolo actual se utilizará como símbolo; el número mágico se establecerá como -1 (para todas las posiciones del símbolo actual sin excepción), mientras que los parámetros de trailing se establecerán en cero.
  • El segundo constructor será paramétrico; en sus parámetros formales, se transmitirán al constructor el nombre del símbolo, el valor del número mágico de la posición y tres parámetros de trailing: inicio, paso y separación.

Vamos a analizar la implementación del constructor paramétrico:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CSimpleTrailing::CSimpleTrailing(const string symbol, const long magic,
                                 const int trail_start, const uint trail_step, const int offset) : m_spread_mlt(2)
  {
   this.SetSymbol(symbol);
   this.m_magic      = magic;
   this.m_trail_start= trail_start;
   this.m_trail_step = trail_step;
   this.m_offset     = offset;
  }

A diferencia del constructor por defecto, en el que, en la línea de inicialización se le asignarán a todas las variables los valores del símbolo actual y valores nulos de los parámetros de trailing, aquí transmitiremos al constructor los valores que precisamente se establecerán en las variables correspondientes. El método SetSymbol() asignará valores a varias variables a la vez:

   void              SetSymbol(const string symbol)
                       {
                        this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol);
                        this.m_point  =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT);
                        this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS);
                       }

En ambos constructores el multiplicador de spread se fijará en 2. De ser necesario, podremos cambiarlo utilizando el método SetSpreadMultiplier().

Método virtual que calcula y retorna el nivel StopLoss de la posición seleccionada:

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CSimpleTrailing::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(tick.bid - this.m_offset * this.m_point);
      case POSITION_TYPE_SELL :  return(tick.ask + this.m_offset * this.m_point);
      default                 :  return 0;
     }
  }

Como se trata de un trailing simple, aquí la posición de Stop Loss se mantendrá siempre a una distancia determinada del precio actual.

Los valores calculados de las posiciones StopLoss se retornarán desde el método y se comprobarán posteriormente en el método CheckCriterion():

//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CSimpleTrailing::CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal or a zero StopLoss is passed, return 'false'
   if(::NormalizeDouble(pos_sl - value_sl, this.m_digits) == 0 || value_sl==0)
      return false;
      
//--- trailing variables
   double trailing_step = this.m_trail_step * this.m_point;                      // convert the trailing step into price
   double stop_level    = this.StopLevel() * this.m_point;                       // convert the symbol StopLevel into price
   int    pos_profit_pt = 0;                                                     // position profit in points

//--- depending on the type of position, check the conditions for modifying StopLoss
   switch(pos_type)
     {
      //--- long position
      case POSITION_TYPE_BUY :
         pos_profit_pt = int((tick.bid - pos_open) / this.m_point);              // calculate the position profit in points
         if(tick.bid - stop_level > value_sl                                     // if the StopLoss level is lower than the price with the StopLevel level set down from it (the distance according to StopLevel is maintained) 
            && pos_sl + trailing_step < value_sl                                 // if the StopLoss level exceeds the trailing step set upwards from the current position StopLoss
            && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start)   // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
           )
            return true;
         break;
         
      //--- short position
      case POSITION_TYPE_SELL :
         pos_profit_pt = int((pos_open - tick.ask) / this.m_point);              // calculate the position profit in points
         if(tick.ask + stop_level < value_sl                                     // if the StopLoss level is higher than the price with the StopLevel level set upwards from it (the distance according to StopLevel is maintained)
            && (pos_sl - trailing_step > value_sl || pos_sl == 0)                // if the StopLoss level is below the trailing step set downwards from the current StopLoss or a position has no StopLoss
            && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start)   // if we trail at any profit or position profit in points exceeds the trailing start, return 'true'
           )
            return true;
         break;
         
      //--- return 'false' by default
      default:
         break;
     }
     
//--- conditions are not met - return 'false'
   return false;
  }

Si el valor StopLoss transmitido al método cumple todos los criterios de todos los filtros, el método retornará true y la posición de Stop Loss se modificará utilizando el método ModifySL():

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool CSimpleTrailing::ModifySL(const ulong ticket, const double stop_loss)
  {
//--- if the EA stop flag is set, report this in the journal and return 'false'
   if(::IsStopped())
     {
      Print("The Expert Advisor is stopped, trading is disabled");
      return false;
     }
//--- if failed to select a position by ticket, report this in the journal and return 'false'
   ::ResetLastError();
   if(!::PositionSelectByTicket(ticket))
     {
      ::PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, ::GetLastError());
      return false;
     }
     
//--- declare the structures of the trade request and the request result
   MqlTradeRequest   request= {};
   MqlTradeResult    result = {};
   
//--- fill in the request structure
   request.action    = TRADE_ACTION_SLTP;
   request.symbol    = ::PositionGetString(POSITION_SYMBOL);
   request.magic     = ::PositionGetInteger(POSITION_MAGIC);
   request.tp        = ::PositionGetDouble(POSITION_TP);
   request.position  = ticket;
   request.sl        = ::NormalizeDouble(stop_loss, this.m_digits);
   
//--- if the trade operation could not be sent, report this to the journal and return 'false'
   if(!::OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, ::GetLastError());
      return false;
     }
     
//--- request to change StopLoss position successfully sent
   return true;
  }

Para obtener el nivel de stop, utilizaremos el método StopLevel():

//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int CSimpleTrailing::StopLevel(void)
  {
   int spread     = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_SPREAD);
   int stop_level = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
   return int(stop_level == 0 ? spread * this.m_spread_mlt : stop_level);
  }

Como el nivel de stop no es un valor constante y puede cambiar dinámicamente, cada vez que llamamos al método, solicitaremos los valores necesarios a las propiedades del símbolo. Si el StopLevel del símbolo es igual a cero, utilizaremos el valor de spread del símbolo multiplicado por 2. Este valor se establecerá por defecto.

Ya creamos los tres métodos anteriores en el último artículo como funciones independientes. Ahora estos métodos estarán ocultos en una sección privada de la clase y realizarán sus funciones sin acceso a ellas desde el exterior.

El método principal de trailing será el método Run(), que iniciará el ciclo de selección de posiciones y desplazamiento de sus stops a los valores calculados:

//+------------------------------------------------------------------+
//| Launch simple trailing with StopLoss offset from the price       |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(void)
  {
//--- if disabled, leave
   if(!this.m_active)
      return false;
//--- trailing variables
   MqlTick tick = {};                           // price structure
   bool    res  = true;                         // result of modification of all positions
//--- check the correctness of the data by symbol
   if(this.m_point==0)
     {
      //--- let's try to get the data again
      ::ResetLastError();
      this.SetSymbol(this.m_symbol);
      if(this.m_point==0)
        {
         ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError());
         return false;
        }
     }
     
//--- in a loop by the total number of open positions
   int total =::PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
     {
      //--- get the ticket of the next position
      ulong  pos_ticket =::PositionGetTicket(i);
      if(pos_ticket == 0)
         continue;
         
      //--- get the symbol and position magic
      string pos_symbol = ::PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = ::PositionGetInteger(POSITION_MAGIC);
      
      //--- if the position does not match the filter by symbol and magic number, leave
      if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol))
         continue;
      
      //--- if failed to get the prices, move on
      if(!::SymbolInfoTick(this.m_symbol, tick))
         continue;

      //--- get the position type, its opening price and StopLoss level
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
      double             pos_open =::PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl   =::PositionGetDouble(POSITION_SL);
      
      //--- get the calculated StopLoss level
      double value_sl = this.GetStopLossValue(pos_type, tick);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable
      if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
         res &=this.ModifySL(pos_ticket, value_sl);
     }
//--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter
   return res;
  }

El método buscará en la lista de posiciones activas del terminal, las filtrará según el símbolo y el número mágico y desplazará los stops de la posición a la distancia calculada del precio actual. El resultado de la modificación del StopLoss de cada posición se añadirá a la variable res. Al final del ciclo de modificación de los stops de todas las posiciones, la variable contendrá una bandera true solo si los stops de todas las posiciones que coinciden con el filtro símbolo/número mágico han sido modificados con éxito. O bien, si al menos una posición no ha sido modificada con éxito, la bandera contendrá el valor false. Esto se hará para controlar de forma adicional la modificación del stop desde el exterior.

Ahora vamos a conectar esta clase al asesor experto para probar su rendimiento.

Para la prueba, tomaremos el asesor experto Moving Average.mq5 de la carpeta \MQL5\Experts\Examples\ del terminal y lo guardaremos con el nombre MovingAverageWithSimpleTrail.mq5.

Luego conectaremos el archivo de la clase de trailing al asesor experto, escribiremos los parámetros adicionales en la configuración y crearemos una instancia de la clase de trailing simple:

//+------------------------------------------------------------------+
//|                                 MovingAverageWithSimpleTrail.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include <Trailings\Trailings.mqh>

input group  " - Moving Averages Expert Parameters -"
input double MaximumRisk        = 0.02;    // Maximum Risk in percentage
input double DecreaseFactor     = 3;       // Descrease factor
input int    MovingPeriod       = 12;      // Moving Average period
input int    MovingShift        = 6;       // Moving Average shift

input group  " - Simple Trailing Parameters -"
input int   InpTrailingStart  =  10;      // Trailing start
input int   InpTrailingStep   =  20;      // Trailing step in points
input int   InpTrailingOffset =  30;      // Trailing offset in points
input bool  InpUseTrailing    =  true;    // Use Trailing Stop

//---
int    ExtHandle=0;
bool   ExtHedging=false;
CTrade ExtTrade;

CSimpleTrailing ExtTrailing;              // simple trailing class instance

#define MA_MAGIC 1234501
//+------------------------------------------------------------------+

En el manejador OnInit(), estableceremos los parámetros de trailing:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- prepare trade class to control positions if hedging mode is active
   ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   ExtTrade.SetExpertMagicNumber(MA_MAGIC);
   ExtTrade.SetMarginMode();
   ExtTrade.SetTypeFillingBySymbol(Symbol());
//--- Moving Average indicator
   ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(ExtHandle==INVALID_HANDLE)
     {
      printf("Error creating MA indicator");
      return(INIT_FAILED);
     }
     
//--- set trailing parameters
   ExtTrailing.SetActive(InpUseTrailing);
   ExtTrailing.SetSymbol(Symbol());
   ExtTrailing.SetMagicNumber(MA_MAGIC);
   ExtTrailing.SetTrailingStart(InpTrailingStart);
   ExtTrailing.SetTrailingStep(InpTrailingStep);
   ExtTrailing.SetStopLossOffset(InpTrailingOffset);
//--- ok
   return(INIT_SUCCEEDED);
  }

En el manejador OnTick(), empezaremos con el trailing:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
   if(SelectPosition())
      CheckForClose();
   else
      CheckForOpen();
      
//--- launch trailing
   ExtTrailing.Run();
  }

Eso es todo lo que necesitaremos poner adicionalmente en el archivo del EA. Luego los compilaremos y ejecutaremos en el simulador de estrategias en el gráfico EURUSD, M15; una sola prueba durante el último año. Todos los ticks sin retrasos de ejecución.

Después estableceremos los parámetros como sigue (el trailing estará desactivado):


Realizaremos la prueba con el trailing desactivado y veremos el resultado:



Ahora pasaremos al uso del trailing:


y haremos exactamente la misma prueba.

El resultado de la prueba ha sido el siguiente:



El resultado ha resultado un poco mejor. Podemos ver que el trailing mejora un poco las estadísticas con estos ajustes.

Ahora, basándonos en el trailing simple que hemos creado, podremos crear cualquier otro trailing.

Así, crearemos varias clases de trailing según los indicadores, según el indicador Parabolic SAR y según distintas medias móviles de la lista de indicadores de tendencia presentada en el terminal de cliente.


Clases de trailing según el indicador

Ahora continuaremos escribiendo código en el mismo archivo. Pero antes de empezar una nueva clase de trailing según el indicador, deberemos recordar que para los trailings según los valores del indicador, necesitaremos crear el propio indicador de cuyos datos obtendremos los valores de los niveles de Stop Loss. En el caso de los indicadores, para crearlos deberemos especificar el símbolo y el marco temporal sobre cuyos datos el indicador construirá sus series de datos. Y también necesitaremos guardar el manejador del indicador creado en una variable para que podamos utilizar este manejador para llamar al indicador y recuperar los datos. La obtención de datos según el manejador no dependerá del tipo de indicador, los datos se obtendrán según la función CopyBuffer() con la indicación del indicador según el manejador.

Todas las acciones para obtener datos del indicador deberán detallarse en las clases de trailing según los indicadores. Pero en este caso todas estas acciones deberán prescribirse en cada clase de cada trailing, trabajando según indicadores diferentes. Es posible, pero no lo mejor. Será mejor crear una clase básica de trailing según los indicadores, que implementará los métodos para obtener los datos del indicador según el manejador y prescribirá algunas variables que serán las mismas para cada indicador (símbolo, marco temporal, índice de serie temporal de los datos y manejador del indicador). Y las propias clases de trailing heredarán de esta clase básica. Entonces, la estructura de la clase estará clasificada: la clase básica organizará el acceso a los datos del indicador según el manejador, y la clase heredada creará el indicador necesario y trabajará con sus datos.

Vamos a seguir escribiendo el código en el archivo Trailings.mqh. Escribiremos una clase básica de trailing según los indicadores:

//+------------------------------------------------------------------+
//| Base class of indicator-based trailings                          |
//+------------------------------------------------------------------+
class CTrailingByInd : public CSimpleTrailing
  {
protected:
   ENUM_TIMEFRAMES   m_timeframe;            // indicator timeframe
   int               m_handle;               // indicator handle
   uint              m_data_index;           // indicator data bar
   string            m_timeframe_descr;      // timeframe description

//--- return indicator data
   double            GetDataInd(void)              const { return this.GetDataInd(this.m_data_index); }
   
//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- set parameters
   void              SetTimeframe(const ENUM_TIMEFRAMES timeframe)
                       {
                        this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
                        this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), 7);
                       }
   void              SetDataIndex(const uint index)                  { this.m_data_index=index;       }
                       
//--- return parameters
   ENUM_TIMEFRAMES   Timeframe(void)                           const { return this.m_timeframe;       }
   uint              DataIndex(void)                           const { return this.m_data_index;      }
   string            TimeframeDescription(void)                const { return this.m_timeframe_descr; }
     
//--- return indicator data from the specified timeseries index
   double            GetDataInd(const int index) const;
                      
//--- constructors
                     CTrailingByInd(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_timeframe(::Period()), 
                                            m_handle(INVALID_HANDLE), m_data_index(1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), 7)) {}
                     
                     CTrailingByInd(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int trail_start, const uint trail_step, const int trail_offset) :
                                    CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_data_index(1), m_handle(INVALID_HANDLE)
                       {
                        this.SetTimeframe(timeframe);
                       }
//--- destructor
                    ~CTrailingByInd(void)
                       {
                        if(this.m_handle!=INVALID_HANDLE)
                          {
                           ::IndicatorRelease(this.m_handle);
                           this.m_handle=INVALID_HANDLE;
                          }
                       }
  };

La clase se heredará de la clase de trailing simple, por lo que tendrá toda la funcionalidad de dicha clase, además se implementarán los métodos necesarios para acceder a los datos del indicador según el manejador.

La sección protegida de la clase contendrá las variables de clase, un método para recibir los datos del indicador según el manejador y un método virtual para calcular los niveles de Stop Loss que redefinirá el método homónimo de la clase padre.

La sección pública de la clase contendrá los métodos para establecer y obtener las propiedades del indicador y los constructores con el destructor.

El constructor por defecto, en su línea de inicialización, transmitirá a la clase padre el símbolo actual, un número mágico con valor -1 (todas las posiciones) y parámetros cero para el trailing. El periodo actual del gráfico se establecerá como el marco temporal del gráfico para calcular el indicador.

En el constructor paramétrico, el símbolo y el periodo del gráfico para calcular el indicador, así como todos los parámetros de trailing se transmitirán en los parámetros formales del constructor.
En el destructor de la clase, la parte calculada del indicador se liberará y la variable que almacena el manejador se establecerá en INVALID_HANDLE.

Método que retorna los datos del indicador con el índice especificado de la serie temporal:

//+------------------------------------------------------------------+
//| Return indicator data from the specified timeseries index        |
//+------------------------------------------------------------------+
double CTrailingByInd::GetDataInd(const int index) const
  {
//--- if the handle is invalid, report this and return "empty value"
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__);
      return EMPTY_VALUE;
     }
//--- get the value of the indicator buffer by the specified index
   double array[1];
   ::ResetLastError();
   if(::CopyBuffer(this.m_handle, 0, index, 1, array)!=1)
     {
      ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError());
      return EMPTY_VALUE;
     }
//--- return the received value
   return array[0];
  }

Usando CopyBuffer(), obtendremos el valor de una barra del indicador según el índice especificado y lo retornaremos si se ha obtenido con éxito. Si se produce un error, enviaremos un mensaje al registro y retornaremos un valor vacío (EMPTY_VALUE).

La función CopyBuffer() trabajará con los datos de cualquier indicador según su manejador. Por consiguiente, este método será universal y se usará sin cambios en todas las clases de trailing según los indicadores.

Método virtual que calcula y retorna el nivel StopLoss de la posición seleccionada:

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CTrailingByInd::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- get the indicator value as a level for StopLoss
   double data=this.GetDataInd();
       
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid);
      case POSITION_TYPE_SELL :  return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask);
      default                 :  return 0;
     }
  }

Este método, en la clase padre, simplemente realizaremos el cálculo de la separación del nivel de Stop Loss a partir del precio. En la misma clase en este método, deberemos obtener los datos del indicador y transmitirlos como un nivel Stop Loss. Si no ha recibido los datos del indicador, el método devolverá el precio actual, lo que no permitirá colocar una posición Stop Loss, ya que el StopLevel no permitirá colocar un stop al precio actual.

Como resultado, esta clase implementará el acceso a los datos del indicador según su manejador y el cálculo del nivel para la posición StopLoss según el valor del indicador, y todos sus métodos estarán disponibles en las clases hijas.

Ahora puede crear clases de trailing según los indicadores que estén basadas en la clase creada.


Clase de trailing según Parabolic SAR

Vamos a seguir escribiendo el código en el mismo archivo \MQL5\Include\Trailings\Trailings.mqh.

La clase de trailing según Parabolic SAR, al igual que las otras clases de trailings según los indicadores, deberá heredarse de la clase básica de trailings según los indicadores:

//+------------------------------------------------------------------+
//| Parabolic SAR position StopLoss trailing class                   |
//+------------------------------------------------------------------+
class CTrailingBySAR : public CTrailingByInd
  {
private:
   double            m_sar_step;             // Parabolic SAR Step parameter
   double            m_sar_max;              // Parabolic SAR Maximum parameter

public:
//--- set Parabolic SAR parameters
   void              SetSARStep(const double step)                   { this.m_sar_step=(step<0.0001 ? 0.0001 : step);   }
   void              SetSARMaximum(const double max)                 { this.m_sar_max =(max <0.0001 ? 0.0001 : max);    }
   
//--- return Parabolic SAR parameters
   double            SARStep(void)                             const { return this.m_sar_step;                          }
   double            SARMaximum(void)                          const { return this.m_sar_max;                           }
   
//--- create Parabolic SAR indicator and return the result
   bool              Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum);

//--- constructors
                     CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0)
                       {
                        this.Initialize(this.m_symbol, this.m_timeframe, 0.02, 0.2);
                       }
                     
                     CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                                    const double sar_step, const double sar_maximum,
                                    const int trail_start, const uint trail_step, const int trail_offset);
//--- destructor
                    ~CTrailingBySAR(){}
  };

En la sección privada de la clase se declararán las variables para almacenar los valores de los parámetros del indicador Parabolic SAR.

La sección pública contendrá los métodos para establecer y retornar los valores de las propiedades del indicador, un método para crear el indicador y dos constructores: por defecto y paramétrico.

En el constructor por defecto, se creará un indicador en el símbolo y periodo actuales del gráfico, y en el trailing se establecerán valores cero para sus parámetros.

En el constructor paramétrico todos los parámetros se transmitirán a través de las variables de entrada del constructor y luego se creará el indicador sobre los parámetros transmitidos al constructor:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CTrailingBySAR::CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                               const double sar_step, const double sar_maximum,
                               const int trail_start, const uint trail_step, const int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, sar_step, sar_maximum);
  }

El método Initialize() creará el indicador Parabolic SAR y retornará el resultado de su creación:

//+------------------------------------------------------------------+
//| create Parabolic SAR indicator and return the result             |
//+------------------------------------------------------------------+
bool CTrailingBySAR::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetSARStep(sar_step);
   this.SetSARMaximum(sar_maximum);
   ::ResetLastError();
   this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError());
     }
   return(this.m_handle!=INVALID_HANDLE);
  }

Vamos a poner a prueba la clase de trailing creada según Parabolic SAR.

Para la prueba, tomaremos el asesor experto \MQL5\Experts\Advisors\ExpertMACD.mq5 del paquete estándar y lo guardaremos con el nuevo nombre ExpertMACDWithTrailingBySAR.mq5 y añadiremos las líneas necesarias de código.

Luego conectaremos el archivo con las clases de trailing, añadiremos las nuevas variables de entrada y declaremos la instancia de la clase de trailing:

//+------------------------------------------------------------------+
//|                                                   ExpertMACD.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMACD.mqh>
#include <Expert\Trailing\TrailingNone.mqh>
#include <Expert\Money\MoneyNone.mqh>

#include <Trailings\Trailings.mqh>

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input group  " - ExpertMACD Parameters -"
input string Inp_Expert_Title            ="ExpertMACD";
int          Expert_MagicNumber          =10981;
bool         Expert_EveryTick            =false;
//--- inputs for signal
input int    Inp_Signal_MACD_PeriodFast  =12;
input int    Inp_Signal_MACD_PeriodSlow  =24;
input int    Inp_Signal_MACD_PeriodSignal=9;
input int    Inp_Signal_MACD_TakeProfit  =50;
input int    Inp_Signal_MACD_StopLoss    =20;

input group  " - Trailing By SAR Parameters -"
input ENUM_TIMEFRAMES   InpTimeframeSAR   =  PERIOD_CURRENT;   // Parabolic SAR Timeframe
input double            InpStepSAR        =  0.02;             // Parabolic SAR Step
input double            InpMaximumSAR     =  0.2;              // Parabolic SAR Maximum
input int               InpTrailingStart  =  0;                // Trailing Start
input uint              InpTrailingStep   =  0;                // Trailing Step
input int               InpTrailingOffset =  0;                // Trailing Offset
input bool              InpUseTrailing    =  true;             // Use Trailing Stop

//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingBySAR ExtTrailing;

En el manejador OnInit(), inicializaremos el trailing según Parabolic SAR:

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trailing
   if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR))
     {
      ExtTrailing.SetMagicNumber(Expert_MagicNumber);
      ExtTrailing.SetTrailingStart(InpTrailingStart);
      ExtTrailing.SetTrailingStep(InpTrailingStep);
      ExtTrailing.SetStopLossOffset(InpTrailingOffset);
      ExtTrailing.SetActive(InpUseTrailing);
     }
   
//--- Initializing expert

En el manejador OnTick(), iniciaremos el trailing:

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   ExtTrailing.Run();
  }

Luego compilaremos el asesor experto y lo ejecutaremos en el simulador con los parámetros por defecto:


Podemos ver como los stops de las posiciones se arrastran correctamente según los valores de la primera barra de Parabolic SAR.

Ahora crearemos las clases de trailing para las diferentes medias móviles representadas en el terminal de cliente.


Clases de trailing según las medias móviles

Estas clases diferirán de la clase de trailing según Parabolic SAR únicamente en el conjunto de parámetros de entrada y por el hecho de que el indicador correspondiente a la clase se creará en el método Initialise().

Partiendo de esta base, consideraremos solo una clase: la clase de media móvil adaptativa:

//+------------------------------------------------------------------+
//| Adaptive Moving Average position StopLoss trailing class         |
//+------------------------------------------------------------------+
class CTrailingByAMA : public CTrailingByInd
  {
private:
   int               m_period;               // Period AMA parameter
   int               m_fast_ema;             // Fast EMA Period parameter
   int               m_slow_ema;             // Slow EMA Period parameter
   int               m_shift;                // Shift AMA parameter
   ENUM_APPLIED_PRICE m_price;               // Applied Price AMA parameter

public:
//--- set AMA parameters
   void              SetPeriod(const uint period)                    { this.m_period=int(period<1 ? 9 : period);     }
   void              SetFastEMAPeriod(const uint period)             { this.m_fast_ema=int(period<1 ? 2 : period);   }
   void              SetSlowEMAPeriod(const uint period)             { this.m_slow_ema=int(period<1 ? 30 : period);  }
   void              SetShift(const int shift)                       { this.m_shift = shift;                         }
   void              SetPrice(const ENUM_APPLIED_PRICE price)        { this.m_price = price;                         }
   
//--- return AMA parameters
   int               Period(void)                              const { return this.m_period;                         }
   int               FastEMAPeriod(void)                       const { return this.m_fast_ema;                       }
   int               SlowEMAPeriod(void)                       const { return this.m_slow_ema;                       }
   int               Shift(void)                               const { return this.m_shift;                          }
   ENUM_APPLIED_PRICE Price(void)                              const { return this.m_price;                          }
   
//--- create AMA indicator and return the result
   bool              Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe,
                                const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price);
   
//--- constructors
                     CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0)
                       {
                        this.Initialize(this.m_symbol, this.m_timeframe, 9, 2, 30, 0, PRICE_CLOSE);
                       }
                     
                     CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                                    const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price,
                                    const int trail_start, const uint trail_step, const int trail_offset);
//--- destructor
                    ~CTrailingByAMA(){}
  };
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CTrailingByAMA::CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic,
                               const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price,
                               const int trail_start, const uint trail_step, const int trail_offset) :
                               CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset)
  {
   this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price);
  }
//+------------------------------------------------------------------+
//| create AMA indicator and return the result                       |
//+------------------------------------------------------------------+
bool CTrailingByAMA::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe,
                                const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price)
  {
   this.SetSymbol(symbol);
   this.SetTimeframe(timeframe);
   this.SetPeriod(period);
   this.SetFastEMAPeriod(fast_ema);
   this.SetSlowEMAPeriod(slow_ema);
   this.SetShift(shift);
   this.SetPrice(price);
   ::ResetLastError();
   this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price);
   if(this.m_handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema,
                    ::StringSubstr(::EnumToString(this.m_price),6), ::GetLastError());
     }
   return(this.m_handle!=INVALID_HANDLE);
  }

Esta clase será absolutamente idéntica a la clase de trailing según Parabolic SAR comentada anteriormente. Cada clase de trailing de medias móviles tendrá su propio conjunto de variables para almacenar los parámetros del indicador y los métodos para establecer y devolver los valores correspondientes a estas variables. Todas las clases de trailing según las medias móviles serán idénticas entre sí, y el lector podrá familiarizarse con ellas mirando los códigos en el archivo de clases de trailing Trailings.mqh adjunto a este artículo. Podrá probar el funcionamiento de la clase de trailing según la media móvil simple ejecutando el asesor experto de prueba del archivo ExpertMACDWithTrailingByMA.mq5, también adjunto al final del artículo.

Bien, ya hemos creado las clases de trailing simples y trailings según los indicadores estándar presentados en el terminal, adecuados para especificar los niveles para colocar posiciones Stop Loss.

Pero estos no son todos los tipos de trailing que se pueden crear usando las clases presentadas. Existen trailings que desplazan los stops de la posición a determinados niveles de precio especificados por separado para posiciones largas y cortas. Un ejemplo de este tipo de trailing podría ser un trailing en velas High/Low, o en un indicador fractal, por ejemplo.

Para aplicar este tipo de trailing, deberemos poder especificar por separado para cada tipo de posición un nivel diferente para fijar un Stop Loss. Vamos a crear una clase de este tipo.


Clase de trailing según los valores de nivel Stop Loss especificados

Todo lo que debemos hacer para crear un trailing según los valores especificados es definir las variables en lugar de los parámetros de indicador para registrar en ellas los valores de Stop Loss de las posiciones largas y cortas. Y en lugar de obtener los datos del indicador, bastará con calcular los valores para las posiciones de Stop Loss a partir de los valores especificados en estas variables. Los valores de las variables deberán introducirse desde el programa de control, directamente al llamar al método Run() del trailing.

Vamos a escribir una clase de este tipo:

//+------------------------------------------------------------------+
//| Trailing class based on a specified value                        |
//+------------------------------------------------------------------+
class CTrailingByValue : public CSimpleTrailing
  {
protected:
   double            m_value_sl_long;     // StopLoss level for long positions
   double            m_value_sl_short;    // StopLoss level for short positions

//--- calculate and return the StopLoss level of the selected position
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- return StopLoss level for (2) long and (2) short positions
   double            StopLossValueLong(void)    const { return this.m_value_sl_long;   }
   double            StopLossValueShort(void)   const { return this.m_value_sl_short;  }

//--- launch trailing with the specified StopLoss offset from the price
   bool              Run(const double value_sl_long, double value_sl_short);

//--- constructors
                     CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {}
                     
                     CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) :
                                      CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {}
//--- destructor
                    ~CTrailingByValue(void){}
  };

Podemos observar que la clase resultará prácticamente idéntica a todas las clases de trailing según los indicadores que hemos mencionado anteriormente. Pero aquí no hay ningún método Initialise(), ya que no será necesario crear ningún indicador.

En el método virtual que calcula y devuelve el nivel de Stop Loss de una posición seleccionada, los valores de Stop Loss se calcularán a partir de los niveles establecidos para los stops de las posiciones largas y cortas:

//+------------------------------------------------------------------+
//| Calculate and return the StopLoss level of the selected position |
//+------------------------------------------------------------------+
double CTrailingByValue::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick)
  {
//--- calculate and return the StopLoss level depending on the position type
   switch(pos_type)
     {
      case POSITION_TYPE_BUY  :  return(this.m_value_sl_long  - this.m_offset * this.m_point);
      case POSITION_TYPE_SELL :  return(this.m_value_sl_short + this.m_offset * this.m_point);
      default                 :  return 0;
     }
  }

En el método Run(), a través de sus parámetros formales, transmitiremos a la clase los valores de Stop Loss para las posiciones largas y para las posiciones cortas:

//+------------------------------------------------------------------+
//| Launch trailing with the specified StopLoss offset from the price|
//+------------------------------------------------------------------+
bool CTrailingByValue::Run(const double value_sl_long, double value_sl_short)
  {
   this.m_value_sl_long =value_sl_long;
   this.m_value_sl_short=value_sl_short;
   return CSimpleTrailing::Run();
  }

En primer lugar, los valores transmitidos en los parámetros formales se escribirán en las variables de miembro de la clase y, a continuación, se llamará al método Run() de la clase padre. El método virtual GetStopLossValue() sobreescrito en esta clase se llamará desde la clase padre, y los stops de la posición se establecerán en los valores calculados en él.


Conectamos un trailing stop a un asesor experto

Ya hemos hablado antes de cómo conectar un trailing al asesor experto, al probar el trailing según los indicadores. Veamos ahora cómo conectar e iniciar un trailing según los valores transmitidos, es decir, según las velas High y Low.

Luego conectaremos el trailing al asesor experto de la entrega estándar \MQL5\Experts\Advisors\ExpertMACD.mq5. Lo guardaremos con el nuevo nombre ExpertMACDWithTrailingByValue.mq5 y realizaremos las mejoras necesarias.

Después conectaremos el archivo con las clases de trailing al asesor experto, añadiremos las variables de entrada para la configuración del trailing y declararemos una instancia de la clase de trailing según los valores especificados:

//+------------------------------------------------------------------+
//|                                                   ExpertMACD.mq5 |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include                                                          |
//+------------------------------------------------------------------+
#include <Expert\Expert.mqh>
#include <Expert\Signal\SignalMACD.mqh>
#include <Expert\Trailing\TrailingNone.mqh>
#include <Expert\Money\MoneyNone.mqh>

#include <Trailings\Trailings.mqh>

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
//--- inputs for expert
input group  " - ExpertMACD Parameters -"
input string Inp_Expert_Title            ="ExpertMACD";
int          Expert_MagicNumber          =10981;
bool         Expert_EveryTick            =false;
//--- inputs for signal
input int    Inp_Signal_MACD_PeriodFast  =12;
input int    Inp_Signal_MACD_PeriodSlow  =24;
input int    Inp_Signal_MACD_PeriodSignal=9;
input int    Inp_Signal_MACD_TakeProfit  =50;
input int    Inp_Signal_MACD_StopLoss    =20;

input group  " - Trailing By Value Parameters -"
input ENUM_TIMEFRAMES   InpTimeframe      =  PERIOD_CURRENT;   // Data Rates Timeframe
input uint              InpDataRatesIndex =  2;                // Data Rates Index for StopLoss
input uint              InpTrailingStep   =  0;                // Trailing Step
input int               InpTrailingStart  =  0;                // Trailing Start
input int               InpTrailingOffset =  0;                // Trailing Offset
input bool              InpUseTrailing    =  true;             // Use Trailing Stop

//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
CTrailingByValue ExtTrailing;

La variable de entrada Data Rates Timeframe será el periodo del gráfico del que se tomarán los precios High para los stops cortos y los precios Low para los stops largos.

La variable de entrada Data Rates Index for StopLoss será el índice de la barra en el gráfico con el Data Rates Timeframe del cual se tomarán los precios High y Low para establecer las posiciones de StopLoss.

En el manejador OnInit() del EA, inicializaremos los parámetros del trailing:

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trailing
   ExtTrailing.SetMagicNumber(Expert_MagicNumber);
   ExtTrailing.SetTrailingStart(InpTrailingStart);
   ExtTrailing.SetTrailingStep(InpTrailingStep);
   ExtTrailing.SetStopLossOffset(InpTrailingOffset);
   ExtTrailing.SetActive(InpUseTrailing);
   
//--- Initializing expert

Solo se arrastrarán los stops de aquellas posiciones cuyo número mágico coincida con el número mágico del EA, es decir, solo arrastraremos las posiciones abiertas por este EA.

En el manejador OnTick() del asesor, obtendremos los datos de la barra con el índice establecido en la variable de entrada InpDataRatesIndex, e iniciaremos el trailing indicando los precios StopLoss (High y Low de la barra) para las posiciones largas y cortas:

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   MqlRates rates[1]={};
   if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,1,rates))
      ExtTrailing.Run(rates[0].low,rates[0].high);
  }

Esto es todo lo que hay que hacer para conectar el trailing al EA. Como ya puede ver, la única diferencia a la hora de conectar los diferentes trailings a los asesores expertos será la declaración de instancias de diferentes tipos de trailings. Todos los demás pasos para conectar las líneas de trailing serán prácticamente iguales y no deberían plantear preguntas ni dudas.

Vamos a compilar el asesor experto y a ejecutarlo en el modo de prueba visual con la configuración predeterminada:

Podemos observar como los stops de la posición se establecen en los valores High y Low de las velas que tienen un índice en la serie temporal 2.

Hasta qué punto un trailing de este tipo puede ofrecer beneficios en el sistema comercial es ya un caso que debe someterse a pruebas independientes.

Además, cada uno puede crear su propio trailing stop según su propio algoritmo con la ayuda de las clases presentadas en el artículo: aquí el espacio para la investigación es prácticamente ilimitado.


Conclusión

Hoy hemos creado clases de diferentes trailing stop que facilitan la conexión de un trailing stop a cualquier EA. Las clases creadas suponen también un buen conjunto de herramientas para crear trailing stops utilizando nuestros propios algoritmos.

En los ejemplos, solo hemos considerado una forma de trabajar con objetos de clase, creando una instancia de un objeto de clase de trailing. Este enfoque permite definir de antemano en el programa qué tipos de trailings serán necesarios y cuáles deben ser los parámetros de los mismos. Para cada trailing, se crea un objeto en el área global. Este planteamiento tiene su razón de ser: es sencillo y directo. Pero para que la creación dinámica de objetos de trailing usando el operador de creación de nuevos objetos sea correcta en cuanto al tiempo de ejecución, será mejor utilizar las facilidades que ofrece la Biblioteca Estándar para la creación de listas de objetos vinculadas. Para ello, todas las clases de trailing se heredan del objeto básico CObject de la biblioteca estándar. Este enfoque puede discutirse en los comentarios al artículo, ya que queda fuera del alcance de este tema.

Todas las clases presentadas en este artículo pueden utilizarse "tal cual" en nuestros propios desarrollos, o podemos modificarlas para adaptarlas a nuestras necesidades y tareas.


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

Análisis de sentimientos y aprendizaje profundo para operar con EA y backtesting con Python Análisis de sentimientos y aprendizaje profundo para operar con EA y backtesting con Python
En este artículo, presentaremos un análisis de sentimiento y los modelos ONNX con Python para ser utilizados en un asesor experto. Un script ejecuta un modelo ONNX entrenado a partir de TensorFlow para predicciones de aprendizaje profundo, mientras que otro obtiene titulares de noticias y cuantifica el sentimiento utilizando IA.
Características del Wizard MQL5 que debe conocer (Parte 26): Medias móviles y el exponente de Hurst Características del Wizard MQL5 que debe conocer (Parte 26): Medias móviles y el exponente de Hurst
El exponente de Hurst es una medida del grado de autocorrelación de una serie temporal a largo plazo. Se entiende que capta las propiedades a largo plazo de una serie temporal y, por tanto, tiene cierto peso en el análisis de series temporales, incluso fuera de las series temporales económicas/financieras. Sin embargo, nos centramos en sus posibles beneficios para los operadores, examinando cómo esta métrica podría combinarse con las medias móviles para crear una señal potencialmente sólida.
Redes neuronales: así de sencillo (Parte 90): Interpolación frecuencial de series temporales (FITS) Redes neuronales: así de sencillo (Parte 90): Interpolación frecuencial de series temporales (FITS)
Al estudiar el método FEDformer, abrimos la puerta al dominio frecuencial de la representación de series temporales. En este nuevo artículo continuaremos con el tema iniciado, y analizaremos un método que permite no solo el análisis, sino también la predicción de estados posteriores en el ámbito privado.
Algoritmo de cola de cometa (Comet Tail Algorithm, CTA) Algoritmo de cola de cometa (Comet Tail Algorithm, CTA)
En este artículo, analizaremos un nuevo algoritmo de optimización de autor, el CTA (Comet Tail Algorithm), que se inspira en objetos espaciales únicos: los cometas y sus impresionantes colas que se forman al acercarse al Sol. Este algoritmo se basa en el concepto del movimiento de los cometas y sus colas, y está diseñado para encontrar soluciones óptimas en problemas de optimización.