Русский 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 |
72 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:

//+------------------------------------------------------------------+
//| Запускает простой трейлинг с отступом StopLoss от цены           |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(void)
  {
//--- если отключен - уходим
   if(!this.m_active)
      return false;
//--- переменные трала
   MqlTick tick = {};                           // структура цен
   bool    res  = true;                         // результат модификации всех позиций
//--- проверка корректности данных по символу
   if(this.m_point==0)
     {
      //--- попробуем ещё раз получить данные
      ::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;
        }
     }
     
//--- в цикле по общему количеству открытых позиций
   int total =::PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
     {
      //--- получаем тикет очередной позиции
      ulong  pos_ticket =::PositionGetTicket(i);
      if(pos_ticket == 0)
         continue;
         
      //--- получаем символ и магик позиции
      string pos_symbol = ::PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = ::PositionGetInteger(POSITION_MAGIC);
      
      //--- если позиция не соответствует фильтру по символу и магику - уходим
      if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol))
         continue;
      
      //--- если цены получить не удалось - идём дальше
      if(!::SymbolInfoTick(this.m_symbol, tick))
         continue;

      //--- получаем тип позиции, цену её открытия и уровень StopLoss
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
      double             pos_open =::PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl   =::PositionGetDouble(POSITION_SL);
      
      //--- получаем рассчитанный уровень StopLoss
      double value_sl = this.GetStopLossValue(pos_type, tick);
      
      //--- если условия для модификации StopLoss подходят - модифицируем стоп позиции и добавляем результат к переменной res
      if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
         res &=this.ModifySL(pos_ticket, value_sl);
     }
//--- по окончании цикла возвращаем результат модификации каждой подходящей по фильтру "символ/магик" позиции
   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:

//+------------------------------------------------------------------+
//| Класс простого трала StopLoss позиций                            |
//+------------------------------------------------------------------+
class CSimpleTrailing : public CObject
  {
private:
//--- проверяет критерии модификации StopLoss позициии и возвращает флаг
   bool              CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick);

//--- модифицирует StopLoss позиции по её тикету
   bool              ModifySL(const ulong ticket, const double stop_loss);

//--- возвращает размер StopLevel в пунктах
   int               StopLevel(void);

protected: 
   string            m_symbol;                        // торговый символ
   long              m_magic;                         // идентификатор эксперта
   double            m_point;                         // Point символа
   int               m_digits;                        // Digits символа
   int               m_offset;                        // дистанция стопа от цены
   int               m_trail_start;                   // прибыль в пунктах для запуска трала
   uint              m_trail_step;                    // шаг трала
   uint              m_spread_mlt;                    // множитель спреда для возврата значения StopLevel
   bool              m_active;                        // флаг активности трала

//--- рассчитывает и возвращает уровень StopLoss выбранной позиции
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- установка параметров трала
   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;       }

//--- возврат параметров трала
   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;       }

//--- запуск трала с отступом StopLoss от цены
   bool              Run(void);
   bool              Run(const ulong pos_ticket);

//--- конструкторы
                     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);
//--- деструктор
                    ~CSimpleTrailing() {}
  };

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

//+------------------------------------------------------------------+
//| Запускает простой трейлинг с отступом StopLoss от цены           |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(const ulong pos_ticket)
  {
//--- если трейлинг отключен, или невалидный тикет - уходим
   if(!this.m_active || pos_ticket==0)
      return false;
      
//--- переменные трала
   MqlTick tick = {};   // структура цен
   
//--- проверка корректности данных по символу
   ::ResetLastError();
   if(this.m_point==0)
     {
      //--- попробуем ещё раз получить данные
      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;
        }
     }
//--- выбираем позицию по тикету
   if(!::PositionSelectByTicket(pos_ticket))
     {
      ::PrintFormat("%s: PositionSelectByTicket(%I64u) failed. Error %d",__FUNCTION__, pos_ticket, ::GetLastError());
      return false;
     }
     
//--- если цены получить не удалось - возвращаем false
   if(!::SymbolInfoTick(this.m_symbol, tick))
      return false;

//--- получаем тип позиции, цену её открытия и уровень StopLoss
   ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
   double             pos_open =::PositionGetDouble(POSITION_PRICE_OPEN);
   double             pos_sl   =::PositionGetDouble(POSITION_SL);
   
//--- получаем рассчитанный уровень StopLoss
   double value_sl = this.GetStopLossValue(pos_type, tick);
   
   //--- если условия для модификации StopLoss подходят - возвращаем результат модифицикации стопа позиции
   if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick))
      return(this.ModifySL(pos_ticket, value_sl));
//--- условия для модификации не подходят
   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--)
     {
      //--- получаем тикет очередной позиции
      ulong  pos_ticket =::PositionGetTicket(i);
      if(pos_ticket == 0)
         continue;
         
      //--- получаем символ и магик позиции
      string pos_symbol = ::PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = ::PositionGetInteger(POSITION_MAGIC);
      
      //--- если позиция не соответствует фильтру по символу и магику - уходим
      if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol))
         continue;
      
      //--- если цены получить не удалось - идём дальше
      if(!::SymbolInfoTick(this.m_symbol, tick))
         continue;

      //--- получаем тип позиции, цену её открытия и уровень StopLoss
      ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
      double             pos_open =::PositionGetDouble(POSITION_PRICE_OPEN);
      double             pos_sl   =::PositionGetDouble(POSITION_SL);
      
      //--- получаем рассчитанный уровень StopLoss
      double value_sl = this.GetStopLossValue(pos_type, tick);
      
      //--- если условия для модификации StopLoss подходят - модифицируем стоп позиции и добавляем результат к переменной res
      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:

//+------------------------------------------------------------------+
//| Запускает простой трейлинг с отступом StopLoss от цены           |
//+------------------------------------------------------------------+
bool CSimpleTrailing::Run(void)
  {
//--- если отключен - уходим
   if(!this.m_active)
      return false;
//--- переменные трала
   bool res  = true; // результат модификации всех позиций
   
//--- проверка корректности данных по символу
   if(this.m_point==0)
     {
      //--- попробуем ещё раз получить данные
      ::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;
        }
     }
     
//--- в цикле по общему количеству открытых позиций
   int total =::PositionsTotal();
   for(int i = total - 1; i >= 0; i--)
     {
      //--- получаем тикет очередной позиции
      ulong  pos_ticket =::PositionGetTicket(i);
      if(pos_ticket == 0)
         continue;
         
      //--- получаем символ и магик позиции
      string pos_symbol = ::PositionGetString(POSITION_SYMBOL);
      long   pos_magic  = ::PositionGetInteger(POSITION_MAGIC);
      
      //--- если позиция не соответствует фильтру по символу и магику - уходим
      if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol))
         continue;
      
      res &=this.Run(pos_ticket);
     }
//--- по окончании цикла возвращаем результат модификации каждой подходящей по фильтру "символ/магик" позиции
   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:

//+------------------------------------------------------------------+
//| Класс трала по указанному значению                               |
//+------------------------------------------------------------------+
class CTrailingByValue : public CSimpleTrailing
  {
protected:
   double            m_value_sl_long;     // значение уровня StopLoss длинных позиций
   double            m_value_sl_short;    // значение уровня StopLoss коротких позиций

//--- рассчитывает и возвращает уровень StopLoss выбранной позиции
   virtual double    GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick);

public:
//--- возвращает уровень StopLoss (2) длинных, (2) коротких позиций
   double            StopLossValueLong(void)    const { return this.m_value_sl_long;   }
   double            StopLossValueShort(void)   const { return this.m_value_sl_short;  }

//--- запуск трала с указанным отступом StopLoss от цены
   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);

//--- конструкторы
                     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) {}
//--- деструктор
                    ~CTrailingByValue(void){}
  };

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

//+------------------------------------------------------------------+
//| Запуск трала с указанным отступом StopLoss от цены               |
//+------------------------------------------------------------------+
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    // Перечисление режимов трейлинга
  {
   TRAILING_MODE_SIMPLE=2, // Простой трал
   TRAILING_MODE_SAR,      // Трал по Parabolic SAR
   TRAILING_MODE_AMA,      // Трал по адартивной скользящей средней
   TRAILING_MODE_DEMA,     // Трал по двойной экспоненциальной скользящей средней
   TRAILING_MODE_FRAMA,    // Трал по фрактальной адартивной скользящей средней
   TRAILING_MODE_MA,       // Трал по простой скользящей средней
   TRAILING_MODE_TEMA,     // Трал по тройной экспоненциальной скользящей средней
   TRAILING_MODE_VIDYA,    // Трал по скользящей средней с динамическим периодом усреднения
  };

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  */ // Указанные значения StopLoss и TakeProfit
  };

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;    // Объект класса трейлингов
   ENUM_TIMEFRAMES   m_timeframe;   // Таймфрейм расчёта индикатора для трала
public:
//--- Устанавливает трал и его параметры
   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[]);
//--- Запускает трал указанной по тикету позиции
   void              Trailing(const ulong pos_ticket);

//--- Конструктор/деструктор
                     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:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
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á:

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CSymbolTradeExt::~CSymbolTradeExt()
  {
//--- удаляем созданный объект трала
   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.

//+------------------------------------------------------------------+
//| Устанавливает параметры трала                                    |
//+------------------------------------------------------------------+
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[])
  {
//--- Устанавливаем параметры трала (для каждого типа индикаторов используются только свои, нужные индикатору, поля структуры)
   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;
   
//--- в зависимости от типа трейлинга создаём объект трала
//---если в качестве периода расчёта передано значение, меньше допустимого, то для каждого индикатора устанавливается его собственное значение по умолчанию
   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;
     }
//--- что-то пошло не так - возвращаем false
   if(this.m_trailing==NULL)
      return false;
      
//--- всё успешно - делаем трал активным и возвращаем 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:

//+------------------------------------------------------------------+
//| Запускает трал указанной по тикету позиции                       |
//+------------------------------------------------------------------+
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  */ // Указанные значения StopLoss и TakeProfit
   TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing                           */ // Простой трал
   TESTING_MODE_TRAIL_SAR,    /* Trailing by Parabolic SAR indicator       */ // Трал по Parabolic SAR
   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                           */ // Отступ StopLoss в пунктах
input    int                  InpTakeProfit     =  500;                 /* TakeProfit in points                         */ // Отступ TakeProfit в пунктах

input    group                " - Trailing Parameters -"
input    bool                 InpSetStopLoss    =  true;                /* Set Initial StopLoss                         */ // Устанавливать StopLoss
input    bool                 InpSetTakeProfit  =  true;                /* Set Initial TakeProfit                       */ // Устанавливать TakeProfit
input    int                  InpTrailingStart  =  150;                 /* Trailing start                               */ // Прибыль в пунктах для старта трейлинга
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                        */ // Таймфрейм индикатора, используемого в расчёте трала
input    int                  InpMAPeriod       =  0;                   /* MA Period                                    */ // Период расчёта скользящей средней
input    int                  InpMAShift        =  0;                   /* MA Shift                                     */ // Горизонтальный сдвиг скользящей средней
input    int                  InpFastEMAPeriod  =  2;                   /* AMA Fast EMA Period                          */ // Период расчёта быстрой EMA адаптивной скользящей средней
input    int                  InpSlowEMAPeriod  =  30;                  /* AMA Slow EMA Period                          */ // Период расчёта медленной EMA адаптивной скользящей средней
input    int                  InpCMOPeriod      =  9;                   /* VIDYA CMO Period                             */ // Период CMO скользящей средней с динамическим периодом усреднения
input    double               InpSARStep        =  0.02;                /* Parabolic SAR Step                           */ // Шаг Parabolic SAR
input    double               InpSARMax         =  0.2;                 /* Parabolic SAR Max                            */ // Максимум Parabolic SAR
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                         */ // Бар данных, получаемых от индикатора

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:

//+------------------------------------------------------------------+
//| Возвращает указатель на торговый объект символа по имени         |
//+------------------------------------------------------------------+
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:

//+------------------------------------------------------------------+
//| Создаёт массив используемых символов                             |
//+------------------------------------------------------------------+
bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols)
  {
   bool res=true;                      // результат
   MqlParam param[7]={};               // параметры тралов
   int total=(int)array_deals.Size();  // общее количество сделок в массиве
   
//--- если массив сделок пустой - возвращаем false
   if(total==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
   
//--- в цикле по массиву сделок
   CSymbolTradeExt *SymbolTrade=NULL;
   for(int i=0; i<total; i++)
     {
      //--- получаем очередную сделку и, если это не покупка и не продажа - идём к следующей
      SDeal deal_str=array_deals[i];
      if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL)
         continue;
      
      //--- найдём торговый объект в списке, у которого символ равен символу сделки
      string symbol=deal_str.Symbol();
      SymbTradeTmp.SetSymbol(symbol);
      list_symbols.Sort();
      int index=list_symbols.Search(&SymbTradeTmp);
      
      //--- если индекс искомого объекта в списке равен -1 - такого объекта в списке нет
      if(index==WRONG_VALUE)
        {
         //--- создаём новый торговый объект символа и, если создать не получилось -
         //--- добавляем к результату значение false и идём к следующей сделке
         SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe);
         if(SymbolTrade==NULL)
           {
            res &=false;
            continue;
           }
         //--- если торговый объект символа не удалось добавить в список -
         //--- удаляем вновь созданный объект, добавляем к результату значение false
         //--- и идём к следующей сделке
         if(!list_symbols.Add(SymbolTrade))
           {
            delete SymbolTrade;
            res &=false;
            continue;
           }
         //--- инициализируем в торговом объекте заданный в настройках трейлинг
         ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode;
         SetTrailingParams(mode, param);
         SymbolTrade.SetTrailing(mode, InpDataIndex, InpTestedMagic, InpTrailingStart, InpTrailingStep, InpTrailingOffset, param);
         
        }
      //--- иначе, если торговый объект уже существует в списке - получаем его по индексу
      else
        {
         SymbolTrade=list_symbols.At(index);
         if(SymbolTrade==NULL)
            continue;
        }
         
      //--- если текущей сделки ещё нет в списке сделок торгового объекта символа
      if(SymbolTrade.GetDealByTime(deal_str.time)==NULL)
        {
         //--- создаём объект сделки по её образцу-структуре
         CDeal *deal=CreateDeal(deal_str);
         if(deal==NULL)
           {
            res &=false;
            continue;
           }
         //--- к значению результата добавляем результат добавления объекта сделки в список сделок торгового объекта символа
         res &=SymbolTrade.AddDeal(deal);
        }
     }
//--- возвращаем итоговый результат создания торговых объектов и добавления сделок в их списки
   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:

//+------------------------------------------------------------------+
//| Устанавливает параметры трала по выбранному его типу             |
//+------------------------------------------------------------------+
void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam &param[])
  {
//--- обнуляем все параметры
   ZeroMemory(param);

//--- в зависимости от выбранного типа трейлинга устанавливаем параметры индикатора
   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                         */ // Устанавливать начальный StopLoss
input    bool                 InpSetTakeProfit  =  true;                /* Set Initial TakeProfit                       */ // Устанавливать начальный TakeProfit

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

//+------------------------------------------------------------------+
//| Торговля по истории                                              |
//+------------------------------------------------------------------+
void TradeByHistory(const string symbol="", const long magic=-1)
  {
   datetime time=0;
   int total=ExtListSymbols.Total();   // количество торговых объектов в списке
   
//--- в цикле по всем торговым объектам символов
   for(int i=0; i<total; i++)
     {
      //--- получаем очередной торговый объект
      CSymbolTradeExt *obj=ExtListSymbols.At(i);
      if(obj==NULL)
         continue;
      
      //--- получаем текущую сделку, на которую указывает индекс списка сделок
      CDeal *deal=obj.GetDealCurrent();
      if(deal==NULL)
         continue;
      
      //--- фильтруем сделку по магику и символу
      if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol))
         continue;
      
      //--- фильтруем сделку по типу (только сделки покупки/продажи)
      ENUM_DEAL_TYPE type=deal.TypeDeal();
      if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL)
         continue;
      
      //--- если это уже обработанная в тестере сделка - идём к следующей
      if(deal.TicketTester()>0)
         continue;
      
      //--- если время сделки ещё не настало - идём к следующему торговому объекту следующего символа
      if(!obj.CheckTime(deal.Time()))
         continue;

      //--- если сделка входа в рынок
      ENUM_DEAL_ENTRY entry=deal.Entry();
      if(entry==DEAL_ENTRY_IN)
        {
         //--- устанавливаем размеры стоп-приказов в зависимости от метода установки стопов
         //--- изначально стоп-приказы не используются (для оригинальной торговли)
         ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
         double sl=0;
         double tp=0;
         //--- если режим установки указанных значений стоп-приказов
         if(InpTestingMode==TESTING_MODE_SLTP)
           {
            //--- получаем корректные значения для StopLoss и TakeProfit
            sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss);
            tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit);
           }
         //--- иначе, если тестирование с трейлингами
         else
           {
            if(InpTestingMode!=TESTING_MODE_ORIGIN)
              {
               //--- если разрешено в настройках, выставляем корректные стоп-приказы открываемым позициям
               if(InpSetStopLoss)
                  sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss);
               if(InpSetTakeProfit)
                  tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit);
              }
           }
         
         //--- открываем позицию по типу сделки
         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(ticket>0)
           {
            //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket);
            //--- получаем идентификатор позиции в тестере и записываем его в свойства объекта сделки
            long pos_id_tester=0;
            if(HistoryDealSelect(ticket))
              {
               pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
               deal.SetPosIDTester(pos_id_tester);
              }
           }
        }
      
      //--- если сделка выхода из рынка
      if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY)
        {
         //--- получаем сделку, на основании которой была открыта позиция
         CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID());
         if(deal_in==NULL)
            continue;

         //--- получаем тикет позиции в тестере из свойств открывающей сделки
         //--- если тикет равен нулю, значит скорее всего позиция в тестере уже закрыта
         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(InpTestingMode==TESTING_MODE_ORIGIN)
           {
            //--- если позиция закрыта по тикету
            if(obj.ClosePos(ticket_tester))
              {
               //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки
               obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
               deal.SetTicketTester(ticket_tester);
              }
           }
         //--- иначе - в тестере работаем со стоп-приказами, выставляемыми по различным алгоритмам, и сделки закрытия пропускаются;
         //--- соответственно, для закрывающей сделки просто увеличиваем количество обработанных тестером сделок и
         //--- записываем тикет сделки в тестере в свойства объекта сделки
         else
           {
            obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1);
            deal.SetTicketTester(ticket_tester);
           }
        }
      //--- если теперь в объекте сделки записан тикет - значит сделка успешно обработана -
      //--- устанавливаем индекс сделки в списке на следующую сделку
      if(deal.TicketTester()>0)
        {
         obj.SetNextDealIndex();
        }
     }
  }

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

//+------------------------------------------------------------------+
//| Трейлинг позиций                                                 |
//+------------------------------------------------------------------+
void Trailing(void)
  {
//--- переменные для получения свойств позиции
   long   magic=-1;
   string symbol="";
   
//--- в цикле по всем позициям
   int total=PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- получаем тикет очередной позиции
      ulong ticket=PositionGetTicket(i);
      if(ticket==0)
         continue;

      //--- получаем магик и символ позиции
      ResetLastError();
      if(!PositionGetInteger(POSITION_MAGIC, magic))
        {
         Print("PositionGetInteger() failed. Error ", GetLastError());
         continue;
        }
      if(!PositionGetString(POSITION_SYMBOL, symbol))
        {
         Print("PositionGetString() failed. Error ", GetLastError());
         continue;
        }
      
      //--- если позиция не проходит по заданным условиям магика и символа - идём к следующей
      if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol))
         continue;
      
      //--- получаем торговый объект по имени символа и вызываем его метод трейлинга позиции по тикету
      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()
  {
//--- работаем только в тестере стратегий
   if(!MQLInfoInteger(MQL_TESTER))
      return;
      
//--- Тралим открытые позиции
   Trailing();
   
//---  Обрабатываем список сделок из файла
   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.32 KB)
SymbolTrade.mqh (54.77 KB)
SymbolTradeExt.mqh (13.02 KB)
MQL5.zip (28.27 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.
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
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.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
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.