English Русский 中文 Deutsch 日本語 Português
preview
Análisis de la negociación a posteriori: ajustando el TrailingStop y los nuevos stops en el simulador de estrategias

Análisis de la negociación a posteriori: ajustando el TrailingStop y los nuevos stops en el simulador de estrategias

MetaTrader 5Ejemplos |
326 4
Artyom Trishkin
Artyom Trishkin

Contenido


Introducción

En el último artículo creamos un asesor experto que comercia en el simulador de estrategias del terminal de cliente basándose en los resultados del trading en una cuenta real. Asimismo, añadimos la posibilidad de establecer nuevos tamaños de StopLoss y TakeProfit para probar su comercio en el simulador con diferentes tamaños de órdenes stop. El resultado fue inesperado: en lugar de pérdidas, obtuvimos un beneficio proporcional a la pérdida real. Por lo tanto, si usáramos en nuestra cuenta los niveles de StopLoss y TakeProfit con los que logramos beneficios en el simulador, el comercio en la cuenta real sería rentable. Y estamos hablando de un simple cambio en las dimensiones de los stops. Me pregunto qué pasaría si añadiéramos un StopLoss trailing? ¿Cómo cambiaría esto el comercio?

Hoy conectaremos varias señales de trailing diferentes al asesor experto y probaremos nuestras transacciones en el simulador de estrategias. Y veremos cuál será el resultado.


Selección y perfeccionamiento de la clase de trailing de posiciones

Intentemos conectar el trailing usando el indicador Parabolic SAR y una media móvil. El indicador Parabolic SAR, a juzgar por su descripción y nombre (SAR = Stop And Reverse), debería ser ideal para desplazar la línea de stop según sus valores:

Parabolic SAR

El indicador técnico Parabolic SAR se desarrolló para analizar los mercados de tendencia. El indicador se representa en un gráfico de precios. Por su significado este indicador es similar a la media móvil, con la única diferencia de que Parabolic SAR se mueve con gran aceleración y puede cambiar su posición respecto al precio. En una tendencia alcista (Up Trend) el indicador estará por debajo de los precios, mientras que en una tendencia bajista (Down Trend) estará por encima de los precios.

Si el precio cruza la línea de Parabolic SAR, el indicador se invertirá y sus próximos valores se situarán al otro lado del precio. En este "viraje" del indicador, el precio máximo o mínimo del periodo anterior servirá como punto de referencia. El viraje del indicador supondrá una señal del final (transición a la corrección o el mercado plano) de la tendencia, o de su inversión.

Parabolic SAR destaca en la identificación de puntos de salida del mercado. Las posiciones largas deberán cerrarse cuando el precio caiga por debajo de la línea del indicador técnico, mientras que las posiciones cortas deberán cerrarse cuando el precio suba por encima de la línea de Parabolic SAR. Es decir, deberemos monitorear la dirección del movimiento de Parabolic SAR y mantener las posiciones abiertas en el mercado solo en la dirección de este movimiento. Este indicador se usa con frecuencia como una línea de trailing stop.

Si hay abierta una posición larga (es decir, el precio está por encima de la línea de Parabolic SAR), la línea del indicador se moverá hacia arriba, independientemente de la dirección en la que se muevan los precios. La magnitud de movimiento de la línea de Parabolic SAR dependerá de la magnitud del movimiento del precio.

Y la media móvil, debido a su desfase natural, también resulta adecuada para situar la línea de stop por detrás del precio. Para una posición larga, si la línea del indicador está por debajo del precio, podemos «tirar» del stop tras el precio según el valor de la media móvil. El precio cruzará la media móvil de forma natural y el stop se activará. Para una posición corta, el stop deberá moverse tras la línea de media móvil si el precio está por debajo de ella.

Ya escribí un artículo con anterioridad sobre la conexión de cualquier trailing a asesores expertos: "Cómo crear cualquier tipo de Trailing Stop y conectarlo a un asesor experto". En este, sugerimos crear clases de trailing y simplemente introducirlas en el EA.

Vamos a aprovechar esta oportunidad. Lo único que no nos conviene mucho es que cada símbolo o número mágico ejecute su propio ciclo de posiciones abiertas. Por lo tanto, deberemos refinar la clase del artículo. Afortunadamente, no es algo difícil.

Vamos a cargar el único archivo que necesitamos de los adjuntos al artículo, Trailings.mqh. En el último artículo, creamos una carpeta donde se encontraban los archivos de clases y los asesores expertos para el trading según la historia de transacciones: \MQL5\Experts\TradingByHistoryDeals. Guardaremos el archivo Trailings.mqh descargado en esta carpeta y lo abriremos en el MetaEditor.

El método principal y primario llamado desde el programa será el método Run(). Si examinamos su código, veremos que aquí se organiza el desbordamiento de las posiciones abiertas:

//+------------------------------------------------------------------+
//| 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;
  }

Si llamamos a este método para cada objeto comercial de un símbolo (véase el primer artículo), obtendremos tantos ciclos de enumeración de posiciones abiertas, como símbolos se han usado en la negociación. Pero esto no es algo óptimo. Haremos que el propio programa contenga un ciclo con todas las posiciones abiertas, y que estos métodos sean llamados desde este ciclo. Pero en el método también hay un ciclo incorporado.... Por lo tanto, necesitaremos crear otro método Run() en la clase de trailing, pero especificando la entrada de la posición cuyo stop debe moverse, y llamarlo desde el ciclo principal.

En la clase de la red de trailing simple declararemos un nuevo método al que se le transmitirá el ticket de la posición requerida:

//+------------------------------------------------------------------+
//| Class of the position StopLoss simple trailing                   |
//+------------------------------------------------------------------+
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: 
   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:
//--- 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);
   bool              Run(const ulong pos_ticket);

//--- 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() {}
  };

Luego escribiremos su implementación fuera del cuerpo de la misma:

//+------------------------------------------------------------------+
//| Launch simple trailing with StopLoss offset from the price       |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(const ulong pos_ticket)
  {
//--- if trailing is disabled, or the ticket is invalid, we leave
   if(!this.m_active || pos_ticket==0)
      return false;
      
//--- trailing variables
   MqlTick tick = {};   // price structure
   
//--- check the correctness of the data by symbol
   ::ResetLastError();
   if(this.m_point==0)
     {
      //--- let's try to get the data again
      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;
        }
     }
//--- select a position by ticket
   if(!::PositionSelectByTicket(pos_ticket))
     {
      ::PrintFormat("%s: PositionSelectByTicket(%I64u) failed. Error %d",__FUNCTION__, pos_ticket, ::GetLastError());
      return false;
     }
     
//--- if prices could not be obtained, return 'false'
   if(!::SymbolInfoTick(this.m_symbol, tick))
      return false;

//--- 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 the conditions for modifying StopLoss are suitable, return the result of modifying the position stop
   if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
      return(this.ModifySL(pos_ticket, value_sl));
//--- conditions for modification are not suitable
   return false;
  }

Ahora este método se llamará o bien desde el método Run(), que no tiene parámetros formales, pero en el que se organiza un ciclo a través de todas las posiciones abiertas, o bien directamente desde el programa transmitiéndole el ticket requerido.

Vamos a mejorar el método Run() sin parámetros formales. Primero eliminaremos un bloque de código del ciclo:

   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);
     }

En su lugar, escribiremos la llamada a un nuevo método:

//+------------------------------------------------------------------+
//| Launch simple trailing with StopLoss offset from the price       |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(void)
  {
//--- if disabled, leave
   if(!this.m_active)
      return false;
//--- trailing variables
   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;
      
      res &=this.Run(pos_ticket);
     }
//--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter
   return res;
  }

En la clase de trailing, en el valor especificado, declararemos de la misma manera un nuevo método Run() con la indicación del ticket de la posición:

//+------------------------------------------------------------------+
//| 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);
   bool              Run(const ulong pos_ticket, 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){}
  };

Y fuera del cuerpo de la clase escribiremos su implementación:

//+------------------------------------------------------------------+
//| Launch trailing with the specified StopLoss offset from the price|
//+------------------------------------------------------------------+
bool CTrailingByValue::Run(const ulong pos_ticket,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(pos_ticket);
  }

Aquí llamaremos al método Run() añadido a la clase de trailing simple, especificando el ticket de la posición.

Le recomiendo familiarizarse con las clases de trailing en el artículo del que hemos tomado el archivo de trailing.

La clase comercial de símbolos creada en el artículo pasado almacena las listas de transacciones y la clase comercial CTrade de la Biblioteca Estándar. Para no mejorarla para la conexión de las clases con los trails, crearemos una nueva clase basada en ella, a la que añadiremos el trabajo con trails.

En el catálogo del terminal \MQL5\Experts\TradingByHistoryDeals\ crearemos el nuevo archivo SymbolTradeExt.mqh de la clase CSymbolTradeExt. Al archivo deberemos conectar el archivo de las clases de los trailings y el archivo de la clase CSymbolTrade, del que deberá heredarse la clase creada:

//+------------------------------------------------------------------+
//|                                               SymbolTradeExt.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 "Trailings.mqh"
#include "SymbolTrade.mqh"

class CSymbolTradeExt : public CSymbolTrade
  {
  }

Al utilizar la clase de trailing tomada del artículo, se nos proporcionarán trailings para Parabolic y todos los tipos de medias móviles estándar. La clase también tendrá un trailing de los valores especificados, pero no la usaremos aquí: requiere su propio cálculo de los niveles de stop en su propio programa y transmitir estos en los parámetros del método llamado Run() de la clase de trailing. Puede ser, por ejemplo, el cálculo de los niveles del indicador ATR y la transmisión de los valores calculados a la clase de trailing.

Vamos a escribir la enumeración de los modos de trailing:

//+------------------------------------------------------------------+
//|                                               SymbolTradeExt.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 "Trailings.mqh"
#include "SymbolTrade.mqh"

enum ENUM_TRAILING_MODE    // Enumeration of trailing modes
  {
   TRAILING_MODE_SIMPLE=2, // Simple trailing
   TRAILING_MODE_SAR,      // Trailing by Parabolic SAR
   TRAILING_MODE_AMA,      // Trailing by adjustable moving average
   TRAILING_MODE_DEMA,     // Trailing by double exponential moving average
   TRAILING_MODE_FRAMA,    // Trailing by fractal adaptive moving average 
   TRAILING_MODE_MA,       // Trailing by simple moving average
   TRAILING_MODE_TEMA,     // Trailing by triple exponential moving average
   TRAILING_MODE_VIDYA,    // Trailing by moving average with dynamic averaging period
  };

class CSymbolTradeExt : public CSymbolTrade
  {
  }

¿Por qué los valores de las constantes de enumeración empiezan con el valor 2 en lugar de cero? El asesor experto que usaremos de base para crear uno nuevo también tiene una lista de modos de prueba:

enum ENUM_TESTING_MODE
  {
   TESTING_MODE_ORIGIN,    /* Original trading                          */ 
   TESTING_MODE_SLTP,      /* Specified StopLoss and TakeProfit values  */ 
  };

Aquí hay dos constantes: el comercio original (0) y el comercio con los valores de órdenes stop indicados (1). Aquí añadiremos más adelante nuevas constantes correspondientes a las constantes de enumeración finales. Por este motivo, los valores de las constantes de enumeración finales comienzan por el valor 2.

En la sección privada de la clase, declararemos el puntero al objeto de la clase trailing y una variable para almacenar el periodo del gráfico para calcular los indicadores utilizados en los trailings. En la sección pública declararemos el método para establecer los parámetros de trailing, el método que inicia la posición de trailing, los constructores y el destructor:

class CSymbolTradeExt : public CSymbolTrade
  {
private:
   CSimpleTrailing  *m_trailing;    // Trailing class object
   ENUM_TIMEFRAMES   m_timeframe;   // Timeframe for calculating the indicator for trailing
public:
//--- Set trailing and its parameters
   bool              SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam &param[]);
//--- Start a trail of the position specified by the ticket
   void              Trailing(const ulong pos_ticket);

//--- Constructor/destructor
                     CSymbolTradeExt() : m_trailing(NULL), m_timeframe(::Period()) { this.SetSymbol(::Symbol()); }
                     CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe);
                    ~CSymbolTradeExt();
  };

En el constructor de la clase, en la línea de inicialización, el símbolo para el que se construye el objeto se transmitirá al constructor de la clase padre, el valor del marco temporal de cálculo del indicador se establecerá en el valor transmitido en los parámetros formales, y el puntero al objeto de la clase de trailing se inicializará con el valor NULL:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSymbolTradeExt::CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe) : CSymbolTrade(symbol)
  {
   this.m_trailing=NULL;
   this.m_timeframe=timeframe;
  }

En el destructor de la clase, si se ha creado un objeto de trailing, se eliminará:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSymbolTradeExt::~CSymbolTradeExt()
  {
//--- delete the created trailing object
   if(this.m_trailing!=NULL)
      delete this.m_trailing;
  }

Como los diferentes tipos de trailing que operan en base a diferentes tipos de indicadores tienen diferentes parámetros de ajuste, pasaremos todo este conjunto de parámetros al método de ajuste de parámetros de trailing a través de la estructura MqlParam, solo que para cada trailing el conjunto de parámetros será diferente, y cada campo de la estructura llevará el valor de su parámetro de indicador inherente solo a él.

//+------------------------------------------------------------------+
//| Set trailing parameters                                          |
//+------------------------------------------------------------------+
bool CSymbolTradeExt::SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam &param[])
  {
//--- Set trailing parameters (only necessary structure fields are used for each indicator type)
   int                ma_period  = (int)param[0].integer_value;
   int                ma_shift   = (int)param[1].integer_value;
   ENUM_APPLIED_PRICE ma_price   = (ENUM_APPLIED_PRICE)param[2].integer_value;
   ENUM_MA_METHOD     ma_method  = (ENUM_MA_METHOD)param[3].integer_value;
   int                fast_ema   = (int)param[4].integer_value;
   int                slow_ema   = (int)param[5].integer_value;
   int                period_cmo = (int)param[6].integer_value;
   double             sar_step   = param[0].double_value;
   double             sar_max    = param[1].double_value;
   
//--- depending on the trailing type, we create a trailing object
//--- if the value passed as the calculation period is less than the allowed value, then each indicator is assigned its own default value
   switch(trailing_mode)
     {
      case TRAILING_MODE_SIMPLE  :
        this.m_trailing=new CSimpleTrailing(this.Symbol(), magic, start, step, offset);
        break;
      case TRAILING_MODE_AMA     :
        this.m_trailing=new CTrailingByAMA(this.Symbol(), this.m_timeframe, magic,
                                           (ma_period<1 ?  9 : ma_period), (fast_ema<1  ?  2 : fast_ema), (slow_ema<1  ? 30 : slow_ema), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_DEMA    :
        this.m_trailing=new CTrailingByDEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_FRAMA   :
        this.m_trailing=new CTrailingByFRAMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_MA      :
        this.m_trailing=new CTrailingByMA(this.Symbol(), this.m_timeframe, magic, ma_period, (ma_period==0 ? 10 : ma_period), ma_method, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_TEMA    :
        this.m_trailing=new CTrailingByTEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_VIDYA   :
        this.m_trailing=new CTrailingByVIDYA(this.Symbol(), this.m_timeframe, magic, (period_cmo<1 ? 9 : period_cmo), (ma_period==0 ? 12 : ma_period), ma_shift, ma_price, start, step, offset);
        break;
      case TRAILING_MODE_SAR     :
        this.m_trailing=new CTrailingBySAR(this.Symbol(), this.m_timeframe, magic, (sar_step<0.0001 ? 0.02 : sar_step), (sar_max<0.02 ? 0.2 : sar_max), start, step, offset);
        break;
      default :
        break;
     }
//--- something went wrong - return 'false'
   if(this.m_trailing==NULL)
      return false;
      
//--- all is well - make the trail active and return 'true'
   this.m_trailing.SetActive(true);
   return true;
  }

Al llamar a este método para diferentes tipos de trailing, deberemos rellenar correctamente la estructura MqlParam. Comprobaremos esto cuando finalicemos el asesor experto para utilizar trailings.

Método que inicia un trailing de la posición especificada por el ticket:

//+------------------------------------------------------------------+
//| Start trailing of the position specified by the ticket           |
//+------------------------------------------------------------------+
void CSymbolTradeExt::Trailing(const ulong pos_ticket)
  {
    if(this.m_trailing!=NULL)
      this.m_trailing.Run(pos_ticket);
  }

Desde el ciclo sobre posiciones abiertas del asesor experto, según el símbolo de la posición, obtendremos de la lista el objeto comercial del símbolo, y desde él llamaremos a este método para arrastrar el stop de la posición cuyo ticket pasaremos al método. Si el objeto de trailing existe, llamaremos a su nuevo método Run() especificando el ticket de posición.


Probamos distintos tipos de TrailingStop

Para la prueba, vamos a tomar el archivo del asesor experto TradingByHistoryDeals_SLTP.mq5del artículo anterior y lo guardaremos en la misma carpeta \MQL5\Experts\TradingByHistoryDeals\ con el nuevo nombre TradingByHistoryDeals_Ext.mq5.

La conexión del archivo de clase CSymbolTrade la sustituiremos por la conexión del archivo de clase CSymbolTradeExt, luego conectaremos el archivo de clases de trailings y ampliaremos la lista de constantes de enumeración de modos de prueba, incluyendo la selección del trailing necesario:

//+------------------------------------------------------------------+
//|                                    TradingByHistoryDeals_Ext.mq5 |
//|                                  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 "SymbolTradeExt.mqh"
#include "Trailings.mqh"

enum ENUM_TESTING_MODE
  {
   TESTING_MODE_ORIGIN,       /* Original trading                          */ 
   TESTING_MODE_SLTP,         /* Specified StopLoss and TakeProfit values  */ 
   TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing                           */ 
   TESTING_MODE_TRAIL_SAR,    /* Trailing by Parabolic SAR indicator       */ 
   TESTING_MODE_TRAIL_AMA,    /* Trailing by AMA indicator                 */ 
   TESTING_MODE_TRAIL_DEMA,   /* Trailing by DEMA indicator                */ 
   TESTING_MODE_TRAIL_FRAMA,  /* Trailing by FRAMA indicator               */ 
   TESTING_MODE_TRAIL_MA,     /* Trailing by MA indicator                  */ 
   TESTING_MODE_TRAIL_TEMA,   /* Trailing by TEMA indicator                */ 
   TESTING_MODE_TRAIL_VIDYA,  /* Trailing by VIDYA indicator               */ 
  };

//+------------------------------------------------------------------+
//| Expert                                                           |
//+------------------------------------------------------------------+

En los parámetros de entrada del asesor experto, añadiremos las variables para la selección de parámetros de trailing:

//+------------------------------------------------------------------+
//| Expert                                                           |
//+------------------------------------------------------------------+
//--- input parameters
input    group                " - Strategy parameters - "
input    string               InpTestedSymbol   =  "";                  /* The symbol being tested in the tester        */ 
input    long                 InpTestedMagic    =  -1;                  /* The magic number being tested in the tester  */ 
sinput   bool                 InpShowDataInLog  =  false;               /* Show collected data in the log               */ 

input    group                " - Stops parameters - "
input    ENUM_TESTING_MODE    InpTestingMode    =  TESTING_MODE_ORIGIN; /* Testing Mode                                 */ 
input    int                  InpStopLoss       =  300;                 /* StopLoss in points                           */ 
input    int                  InpTakeProfit     =  500;                 /* TakeProfit in points                         */ 

input    group                " - Trailing Parameters -"
input    bool                 InpSetStopLoss    =  true;                /* Set Initial StopLoss                         */ 
input    bool                 InpSetTakeProfit  =  true;                /* Set Initial TakeProfit                       */ 
input    int                  InpTrailingStart  =  150;                 /* Trailing start                               */ // Profit in points to start trailing
input    int                  InpTrailingStep   =  50;                  /* Trailing step in points                      */ 
input    int                  InpTrailingOffset =  0;                   /* Trailing offset in points                    */ 

input    group                " - Indicator Parameters -"
input    ENUM_TIMEFRAMES      InpIndTimeframe   =  PERIOD_CURRENT;      /* Indicator's timeframe                        */ // Timeframe of the indicator used in trailing calculation
input    int                  InpMAPeriod       =  0;                   /* MA Period                                    */ 
input    int                  InpMAShift        =  0;                   /* MA Shift                                     */ 
input    int                  InpFastEMAPeriod  =  2;                   /* AMA Fast EMA Period                          */ 
input    int                  InpSlowEMAPeriod  =  30;                  /* AMA Slow EMA Period                          */ 
input    int                  InpCMOPeriod      =  9;                   /* VIDYA CMO Period                             */ 
input    double               InpSARStep        =  0.02;                /* Parabolic SAR Step                           */ 
input    double               InpSARMax         =  0.2;                 /* Parabolic SAR Max                            */ 
input    ENUM_APPLIED_PRICE   InpAppliedPrice   =  PRICE_CLOSE;         /* MA Applied Price                             */ 
input    ENUM_MA_METHOD       InpMAMethod       =  MODE_SMA;            /* MA Smoothing Method                          */ 
input    int                  InpDataIndex      =  1;                   /* Indicator data index                         */ // Bar of data received frrom the indicator


Por defecto, aquí InpMAPeriod se establecerá en cero. Esto se debe a que cada tipo de media móvil tiene un valor de periodo por defecto diferente. Y, como al darse un valor cero de este parámetro, a la clase de trailing se le transmite el valor predeterminado correspondiente al indicador, todos los datos serán válidos si queremos utilizar los valores predeterminados simples para el indicador utilizado en el final.

En todo el código, sustituiremos todas las apariciones de la cadena "CSymbolTrade" por "CSymbolTradeExt". Ahora tendremos una nueva clase de objeto comercial de símbolos CSymbolTradeExt heredada de la anterior clase CSymbolTrade escrita en el último artículo. Y en esta clase, se declarará un objeto de la clase de trailing. Por lo tanto, sustituiremos el tipo de la clase anterior por el nuevo. De hecho, no será necesario hacerlo en todas partes, sino solo donde se requiera el trailing. Pero no vamos a entrar aquí en los detalles sobre la herencia de clases, sino que sustituiremos la clase antigua por una nueva.

Los trailings se llamarán para cada posición según su ticket. Y para acceder al trailing de las posiciones, deberemos obtener de la lista el objeto comercial del símbolo para el que está abierta la posición. Para ello, escribamos una función que retornará el puntero a un objeto comercial de la lista según el nombre de símbolo:

//+------------------------------------------------------------------+
//| Return the pointer to the symbol trading object by name          |
//+------------------------------------------------------------------+
CSymbolTrade *GetSymbolTrade(const string symbol, CArrayObj *list)
  {
   SymbTradeTmp.SetSymbol(symbol);
   list.Sort();
   int index=list.Search(&SymbTradeTmp);
   return list.At(index);
  }

A la función se le transmitirá el nombre del símbolo para el que se desea devolver el objeto comercial, así como el puntero a una lista que almacenará los punteros a los objetos comerciales de los símbolos. El nombre del símbolo transmitido a la función se fijará en el objeto comercial temporal, y en la lista se buscará el índice del objeto con este nombre de símbolo. Como resultado, se retornará el puntero al objeto buscado de la lista según el índice. Si tal objeto no está en la lista, el índice será -1 y se devolverá un valor NULL de la lista.

Al crear un array de símbolos utilizados, se crearán los objetos comerciales de los símbolos, y los símbolos de trailing especificados en los parámetros de entrada deberán inicializarse en ellos. Vamos a mejorar la función que crea un array de símbolos utilizados:

//+------------------------------------------------------------------+
//| Creates an array of used symbols                                 |
//+------------------------------------------------------------------+
bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols)
  {
   bool res=true;                      // result
   MqlParam param[7]={};               // trailing parameters
   int total=(int)array_deals.Size();  // total number of deals in the array
   
//--- if the deal array is empty, return 'false'
   if(total==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
   
//--- in a loop through the deal array
   CSymbolTradeExt *SymbolTrade=NULL;
   for(int i=0; i<total; i++)
     {
      //--- get the next deal and, if it is neither buy nor sell, move on to the next one
      SDeal deal_str=array_deals[i];
      if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL)
         continue;
      
      //--- find a trading object in the list whose symbol is equal to the deal symbol
      string symbol=deal_str.Symbol();
      SymbTradeTmp.SetSymbol(symbol);
      list_symbols.Sort();
      int index=list_symbols.Search(&SymbTradeTmp);
      
      //--- if the index of the desired object in the list is -1, there is no such object in the list
      if(index==WRONG_VALUE)
        {
         //--- we create a new trading symbol object and, if creation fails,
         //--- add 'false' to the result and move on to the next deal
         SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe);
         if(SymbolTrade==NULL)
           {
            res &=false;
            continue;
           }
         //--- if failed to add a symbol trading object to the list,
         //--- delete the newly created object, add 'false' to the result
         //--- and we move on to the next deal
         if(!list_symbols.Add(SymbolTrade))
           {
            delete SymbolTrade;
            res &=false;
            continue;
           }
         //--- initialize trailing specified in the settings in the trading object
         ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode;
         SetTrailingParams(mode, param);
         SymbolTrade.SetTrailing(mode, InpDataIndex, InpTestedMagic, InpTrailingStart, InpTrailingStep, InpTrailingOffset, param);
         
        }
      //--- otherwise, if the trading object already exists in the list, we get it by index
      else
        {
         SymbolTrade=list_symbols.At(index);
         if(SymbolTrade==NULL)
            continue;
        }
         
      //--- if the current deal is not yet in the list of deals of the symbol trading object
      if(SymbolTrade.GetDealByTime(deal_str.time)==NULL)
        {
         //--- create a deal object according to its sample structure
         CDeal *deal=CreateDeal(deal_str);
         if(deal==NULL)
           {
            res &=false;
            continue;
           }
         //--- add the result of adding the deal object to the list of deals of a symbol trading object to the result value
         res &=SymbolTrade.AddDeal(deal);
        }
     }
//--- return the final result of creating trading objects and adding deals to their lists
   return res;
  }

Aquí hemos añadido una declaración de la estructura de parámetros de entrada del indicador y un pequeño bloque de código donde se inicializará el trailing en el objeto comercial.

Para establecer los parámetros del trailing seleccionado en los ajustes, escribiremos una función especial:

//+------------------------------------------------------------------+
//| Set the trailing parameters according to its selected type       |
//+------------------------------------------------------------------+
void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam &param[])
  {
//--- reset all parameters
   ZeroMemory(param);

//--- depending on the selected trailing type, we set the indicator parameters
   switch(mode)
     {
      case TRAILING_MODE_SAR     :
        param[0].type=TYPE_DOUBLE;
        param[0].double_value=InpSARStep;
        
        param[1].type=TYPE_DOUBLE;
        param[1].double_value=InpSARMax;
        break;
      
      case TRAILING_MODE_AMA     :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;

        param[4].type=TYPE_INT;
        param[4].integer_value=InpFastEMAPeriod;

        param[5].type=TYPE_INT;
        param[5].integer_value=InpSlowEMAPeriod;
        break;
      
      case TRAILING_MODE_DEMA    :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;
        break;
      
      case TRAILING_MODE_FRAMA   :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;
        break;
      
      case TRAILING_MODE_MA      :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;

        param[3].type=TYPE_INT;
        param[3].integer_value=InpMAMethod;
        break;
      
      case TRAILING_MODE_TEMA    :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;
        break;
      
      case TRAILING_MODE_VIDYA   :
        param[0].type=TYPE_INT;
        param[0].integer_value=InpMAPeriod;
        
        param[1].type=TYPE_INT;
        param[1].integer_value=InpMAShift;

        param[2].type=TYPE_INT;
        param[2].integer_value=InpAppliedPrice;

        param[6].type=TYPE_INT;
        param[6].integer_value=InpCMOPeriod;
        break;
         
      case TRAILING_MODE_SIMPLE  :
        break;
      
      default:
        break;
     }
  }

Aquí, para cada uno de los tipos de trailing, para los indicadores usados en el trailing, se establecerán en la estructura los valores indicados en los parámetros de entrada del asesor experto. Los valores asignados a los campos de la estructura se comprobarán y corregirán en la nueva clase CSymbolTradeExt del objeto comercial de símbolos que hemos comentado anteriormente.

Vamos a terminar la función para comerciar según la historia de transacciones. Ahora deberemos distinguir claramente qué tipo de prueba se utiliza.
Para probar el comercio original no es necesario colocar órdenes stop,
todas las posiciones se cerrarán según el cierre de las transacciones históricas.

Si se está probando el comercio con órdenes stop de otros tamaños, deberemos obtener sus tamaños correctos y establecerlos para la posición abierta. En el caso de las pruebas de trailing, no será necesario fijar stops, pero para evitar reducciones y "esperas" excesivamente grandes, recomendamos fijar stops iniciales, y luego desplazarlos usando trailings según la lógica establecida. En la configuración del asesor experto hay parámetros que determinan la necesidad de establecer órdenes stop iniciales:

input    bool                 InpSetStopLoss    =  true;                /* Set Initial StopLoss                         */ 
input    bool                 InpSetTakeProfit  =  true;                /* Set Initial TakeProfit                       */ 

Al utilizarlos, también necesitaremos obtener los tamaños de stop correctos y ajustarlos a la posición que estamos abriendo:

//+------------------------------------------------------------------+
//| Trading by history                                               |
//+------------------------------------------------------------------+
void TradeByHistory(const string symbol="", const long magic=-1)
  {
   datetime time=0;
   int total=ExtListSymbols.Total();   // number of trading objects in the list
   
//--- in a loop by all symbol trading objects
   for(int i=0; i<total; i++)
     {
      //--- get another trading object
      CSymbolTradeExt *obj=ExtListSymbols.At(i);
      if(obj==NULL)
         continue;
      
      //--- get the current deal pointed to by the deal list index
      CDeal *deal=obj.GetDealCurrent();
      if(deal==NULL)
         continue;
      
      //--- sort the deal by magic number and symbol
      if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol))
         continue;
      
      //--- sort the deal by type (only buy/sell deals)
      ENUM_DEAL_TYPE type=deal.TypeDeal();
      if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL)
         continue;
      
      //--- if this is a deal already handled in the tester, move on to the next one
      if(deal.TicketTester()>0)
         continue;
      
      //--- if the deal time has not yet arrived, move to the next trading object of the next symbol
      if(!obj.CheckTime(deal.Time()))
         continue;

      //--- in case of a market entry deal
      ENUM_DEAL_ENTRY entry=deal.Entry();
      if(entry==DEAL_ENTRY_IN)
        {
         //--- set the sizes of stop orders depending on the stop setting method
         //--- stop orders are not used initially (for original trading)  
         ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
         double sl=0;
         double tp=0;
         //--- in case of the mode for setting the specified stop order values 
         if(InpTestingMode==TESTING_MODE_SLTP)
           {
            //--- get correct values for StopLoss and TakeProfit
            sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss);
            tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit);
           }
         //--- otherwise, if testing with trailing stops
         else
           {
            if(InpTestingMode!=TESTING_MODE_ORIGIN)
              {
               //--- if allowed in the settings, we set correct stop orders for the positions being opened
               if(InpSetStopLoss)
                  sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss);
               if(InpSetTakeProfit)
                  tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit);
              }
           }
         
         //--- open a position by deal type
         ulong ticket=(type==DEAL_TYPE_BUY  ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : 
                       type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0);
         
         //--- if a position is opened (we received its ticket)
         if(ticket>0)
           {
            //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object 
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket);
            //--- get the position ID in the tester and write it to the properties of the deal object
            long pos_id_tester=0;
            if(HistoryDealSelect(ticket))
              {
               pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
               deal.SetPosIDTester(pos_id_tester);
              }
           }
        }
      
      //--- in case of a market exit deal
      if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY)
        {
         //--- get a deal a newly opened position is based on
         CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID());
         if(deal_in==NULL)
            continue;

         //--- get the position ticket in the tester from the properties of the opening deal
         //--- if the ticket is zero, then most likely the position in the tester is already closed
         ulong ticket_tester=deal_in.TicketTester();
         if(ticket_tester==0)
           {
            PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester());
            obj.SetNextDealIndex();
            continue;
           }
         //--- if we reproduce the original trading history in the tester,
         if(InpTestingMode==TESTING_MODE_ORIGIN)
           {
            //--- if the position is closed by ticket
            if(obj.ClosePos(ticket_tester))
              {
               //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object 
               obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
               deal.SetTicketTester(ticket_tester);
              }
           }
         //--- otherwise, in the tester we work with stop orders placed according to different algorithms, and closing deals are skipped;
         //--- accordingly, for the closing deal, we simply increase the number of deals handled by the tester and
         //--- write the deal ticket in the tester to the properties of the deal object
         else
           {
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket_tester);
           }
        }
      //--- if a ticket is now set in the deal object, then the deal has been successfully handled -
      //--- set the deal index in the list to the next deal
      if(deal.TicketTester()>0)
        {
         obj.SetNextDealIndex();
        }
     }
  }

Ahora escribiremos una función que realizará el trailing de la posición:

//+------------------------------------------------------------------+
//| Trail positions                                                  |
//+------------------------------------------------------------------+
void Trailing(void)
  {
//--- variables for getting position properties
   long   magic=-1;
   string symbol="";
   
//--- in a loop through all positions
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- get the ticket of the next position
      ulong ticket=PositionGetTicket(i);
      if(ticket==0)
         continue;

      //--- get the magic number and position symbol
      ResetLastError();
      if(!PositionGetInteger(POSITION_MAGIC, magic))
        {
         Print("PositionGetInteger() failed. Error ", GetLastError());
         continue;
        }
      if(!PositionGetString(POSITION_SYMBOL, symbol))
        {
         Print("PositionGetString() failed. Error ", GetLastError());
         continue;
        }
      
      //--- if the position does not meet the specified conditions of the magic number and symbol, we move to the next one
      if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol))
         continue;
      
      //--- get a trading object by a symbol name and call its method for trailing a position by ticket
      CSymbolTradeExt *obj=GetSymbolTrade(symbol, &ExtListSymbols);
      if(obj!=NULL)
         obj.Trailing(ticket);
     }
  }

Bueno, aquí todo es sencillo: seleccionaremos cada posición en el ciclo, comprobaremos que su número mágico y su símbolo se correspondan con los introducidos en los ajustes del asesor experto y, si es la posición deseada^, obtendremos el objeto comercial del símbolo de esta posición y llamaremos desde ella el trailing con la indicación del ticket de la posición.

Escribiremos la llamada a esta función en el manejador OnTick() del asesor experto:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- work only in the strategy tester
   if(!MQLInfoInteger(MQL_TESTER))
      return;
      
//--- Trail open positions
   Trailing();
   
//---  Handle the list of deals from the file
   TradeByHistory(InpTestedSymbol, InpTestedMagic);
  }

Y eso es todo, el asesor experto está listo. El código completo del asesor experto se puede encontrar en los archivos adjuntos al artículo.

Probaremos diferentes tipos de trailings y compararemos los resultados del comercio original y del comercio con trailings de posición utilizando diferentes algoritmos.

Al ejecutar el asesor experto en el gráfico de símbolos, este recopilará la historia de transacciones, la escribirá en un archivo y mostrará una alerta indicando la configuración deseada del simulador: fecha inicial de la prueba, depósito inicial y apalancamiento:

El registro mostrará los símbolos utilizados en el comercio y el número de transacciones realizadas con cada símbolo:

Alert: Now you can run testing
Interval: 2024.09.13 - current date
Initial deposit: 3000.00, leverage 1:500
Symbols used in trading:
  1. AUDUSD trade object. Total deals: 222
  2. EURJPY trade object. Total deals: 120
  3. EURUSD trade object. Total deals: 526
  4. GBPUSD trade object. Total deals: 352
  5. NZDUSD trade object. Total deals: 182
  6. USDCAD trade object. Total deals: 22
  7. USDCHF trade object. Total deals: 250
  8. USDJPY trade object. Total deals: 150
  9. XAUUSD trade object. Total deals: 118

Ahora ejecutaremos el asesor experto en el simulador con los parámetros recomendados para el simulador especificados en la alerta y los parámetros duplicados en el registro.

Comercio original:

Vemos que el comercio original ha traído unas pérdidas de 658 dólares.

Vamos a probar con un trailing diferente. Permitiremos un tamaño de stop inicial de 100 pips, y ejecutaremos cada trailing en el simulador.
Solo uno cada vez, sin cambiar ningún otro parámetro, y veremos cómo cambia el comercio.

Trailing simple:

Hemos tenido unas pérdidas de 746,1 dólares. Incluso más que en el comercio original.

Vamos a comprobar los trailings de los indicadores.

Trailing de Parabolic SAR:

El beneficio es de 541,8 dólares.

Veamos ahora los trailings basados en diferentes tipos de medias móviles.

Trailing de AMA:

El beneficio es de 806,5 dólares.

Trailing de DEMA:

El beneficio es de 1.397,1 dólares.

Trailing de FRAMA:

El beneficio es de 1.291,6 dólares.

Trailing de MA:

El beneficio es de 563,1 dólares.

Trailing de TEMA:

El beneficio es de 1.355,1 dólares.

Trailing de VIDYA:

El beneficio es de 283,3 dólares.

Así, la tabla final muestra los resultados del uso de diferentes tipos de trailings de StopLoss en relación con el comercio original:

#
Tipo de trailing
Tamaño de StopLoss
Tamaño de TakeProfit
 Beneficio total
1
Comercio original
100
500
 - 658.0
2
TrailingStop simple
Inicialmente 100, luego stop a 100 pips del precio
0
 - 746.1
3
TrailingStop de Parabolic SAR
Inicialmente 100, luego stop según el valor del indicador
0
 + 541.8
4
TrailingStop de VIDYA
Inicialmente 100, luego stop según el valor del indicador
0
 -283.3
5
TrailingStop de MA
Inicialmente 100, luego stop según el valor del indicador
0
 + 563.1
6
TrailingStop de AMA
Inicialmente 100, luego stop según el valor del indicador
0
 + 806.5
7
TrailingStop de FRAMA
Inicialmente 100, luego stop según el valor del indicador
0
 + 1291.6
8
TrailingStop de TEMA
Inicialmente 100, luego stop según el valor del indicador
0
 + 1355.1
9
TrailingStop de DEMA
Inicialmente 100, luego stop según el valor del indicador
0
 + 1397.1

Como resultado, cuando el comercio original no resulta rentable, el trailing simple habitual, un análogo aproximado del trailing en el terminal cliente, arroja unas pérdidas aún mayores. Bien, veamos un trailing basado en Parabolic SAR, posicionado como un indicador que muestra retrocesos y niveles de stop (Stop And Reverse). Sí, siguiendo el nivel de StopLoss detrás de la línea indicadora en la primera barra, hemos obtenido un beneficio de 540 $.

Veamos ahora los resultados del desplazamiento de los stops de posición según los valores de los diferentes tipos de medias móviles en la primera barra. El trailing del indicador VIDYA ha mostrado unas pérdidas de 280 dólares, pero todos los demás han mostrado beneficios. Además, el seguimiento del stop detrás de la media móvil simple ha dado un beneficio mayor que el trailing sobre Parabolic SAR. Y el "campeón" absoluto en la mejora de los resultados comerciales ha sido la media móvil exponencial doble, +1397 dólares de beneficio.


Conclusión

Hoy hemos creado un asesor experto que nos permite probar nuestras transacciones en el simulador de estrategias y elegir el stop trailing de posición más adecuado para nuestro estilo comercial. Debemos considerar que todos los parámetros de indicadores utilizados en las pruebas de trailing tenían valores estándar y que los ajustes de los trailings se eligieron al azar, "a ojo".

La solución más correcta sería realizar pruebas para cada símbolo individual, y seleccionar una configuración de trailings aceptable considerando el movimiento y la volatilidad del par de divisas. Pero este asesor experto no tiene ajustes separados para establecer sus "propios" parámetros para cada símbolo individual negociado. Sin embargo, lo más importante es que hemos demostrado que es posible probar, analizar y mejorar las transacciones con la ayuda del simulador de estrategias. Y esto significa que usted podrá mejorar aún más el asesor experto para establecer sus propios parámetros para cada símbolo: no es algo difícil, lo más importante es querer.

Todos los archivos de la clase y el asesor experto analizados se adjuntan al artículo. También se adjunta un archivo del cual, tras descomprimirlo, podremos obtener de inmediato los archivos instalados para realizar la prueba en las carpetas necesarias del terminal.

Programas utilizados en el artículo:

#
Nombre
 Tipo  Descripción
1
Trailings.mqh
Biblioteca de clases
Biblioteca de clases de trailing
2
SymbolTrade.mqh
Biblioteca de clases
Biblioteca de clases de estructuras y transacciones, clase comercial del símbolo
3
SymbolTradeExt.mqh
Biblioteca de clases
Símbolo y clase comercial
4
TradingByHistoryDeals_Ext.mq5
Asesor
Asesor experto para ver y cambiar StopLoss, TakeProfit y trailings en el simulador de estrategias de transacciones y operaciones negociadas en la cuenta.
5
MQL5.zip
Archivo
Archivo con los ficheros presentados anteriormente para desempaquetar en el directorio MQL5 del terminal cliente.

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

Archivos adjuntos |
Trailings.mqh (101.41 KB)
SymbolTrade.mqh (53.86 KB)
SymbolTradeExt.mqh (12.95 KB)
MQL5.zip (26.49 KB)
Roman Shiredchenko
Roman Shiredchenko | 30 ene 2025 en 15:38
Muchas gracias por el artículo. Se lee de un tirón y todo queda claro a la primera.
Voy a utilizar los fragmentos de código y f -- ii en mis robots de licitación. También arrastre simple.
Uno en unos robots y otro de arrastre en otros.
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 30 ene 2025 en 23:21
Artículo destacado
den2008224
den2008224 | 31 ene 2025 en 05:16

Трал по VIDYA:

Beneficio - 283,3 dólares.

Error: Pérdida - 283,3 dólares.

Alexey Viktorov
Alexey Viktorov | 31 ene 2025 en 06:46
den2008224 #:

Error: Pérdida de 283,3 dólares.

En el artículo está escrito el valor negativo del beneficio.

Sin embargo, el espacio después del signo menos se insertó accidentalmente.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 4): Analytics Forecaster EA Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 4): Analytics Forecaster EA
Estamos pasando de simplemente ver las métricas analizadas en gráficos a una perspectiva más amplia que incluye la integración de Telegram. Esta mejora permite que los resultados importantes se envíen directamente a tu dispositivo móvil a través de la aplicación Telegram. Acompáñenos en este viaje que exploraremos juntos en este artículo.
Perspectivas bursátiles a través del volumen: Confirmación de tendencias Perspectivas bursátiles a través del volumen: Confirmación de tendencias
La técnica mejorada de confirmación de tendencias combina la acción del precio, el análisis del volumen y el aprendizaje automático para identificar movimientos genuinos del mercado. Requiere tanto rupturas de precios como aumentos de volumen (un 50% por encima de la media) para la validación de las operaciones, al tiempo que utiliza una red neuronal LSTM para obtener una confirmación adicional. El sistema emplea el dimensionamiento de posiciones basado en ATR y la gestión dinámica del riesgo, lo que lo hace adaptable a diversas condiciones del mercado y permite filtrar las señales falsas.
Algoritmo de trading evolutivo con aprendizaje por refuerzo y extinción de individuos no rentables (ETARE) Algoritmo de trading evolutivo con aprendizaje por refuerzo y extinción de individuos no rentables (ETARE)
Hoy le presentamos un innovador algoritmo comercial que combina algoritmos evolutivos con aprendizaje profundo por refuerzo para la negociación de divisas. El algoritmo utiliza un mecanismo de extinción de individuos ineficaces para optimizar la estrategia comercial.
Redes neuronales en el trading: Aprendizaje contextual aumentado por memoria (Final) Redes neuronales en el trading: Aprendizaje contextual aumentado por memoria (Final)
Hoy finalizaremos la implementación del framework MacroHFT para el comercio de criptomonedas de alta frecuencia, que utiliza el aprendizaje de refuerzo consciente del contexto y el aprendizaje con memoria para adaptarse a las condiciones dinámicas del mercado. Y al final de este artículo, probaremos los enfoques aplicados con datos históricos reales para evaluar su eficacia.