English Русский 中文 Español Deutsch 日本語
preview
Análise pós-fato da negociação: ajustando TrailingStop e novos stops no testador de estratégias

Análise pós-fato da negociação: ajustando TrailingStop e novos stops no testador de estratégias

MetaTrader 5Exemplos |
191 4
Artyom Trishkin
Artyom Trishkin

Conteúdo


Introdução

No último artigo, criamos um EA que opera no testador de estratégias do terminal cliente com base nos resultados da negociação em conta real. Adicionamos a possibilidade de definir novos valores de StopLoss e TakeProfit para testar a própria negociação no testador com tamanhos diferentes desses stops. O resultado foi inesperado: em vez de prejuízo, obteve-se lucro, equivalente ao prejuízo real anteriormente registrado. Isso significa que, se tivéssemos usado níveis de StopLoss e TakeProfit como os que deram lucro no testador, também teríamos tido lucro na conta real. E tudo isso com uma simples alteração no tamanho dos stops. Fica então a curiosidade: e se adicionarmos um trailing ao StopLoss? Como isso mudaria a negociação?

Hoje vamos integrar ao EA diferentes métodos de trailing e testar nossa negociação no testador de estratégias. Vamos ver qual será o resultado obtido.


Escolhendo e aprimorando a classe de trailing das posições

Vamos tentar integrar um trailing baseado no indicador Parabolic SAR e na média móvel. O indicador Parabolic SAR, de acordo com sua descrição e pelo próprio nome (SAR = Stop And Reverse), deve se encaixar perfeitamente no deslocamento da linha de stop com base em seus valores:

Parabolic SAR

O indicador técnico Sistema Parabólico SAR (Parabolic SAR) foi desenvolvido para análise de mercados com tendência. O indicador é desenhado no gráfico de preços. Em essência, é semelhante à média móvel, com a diferença de que o Parabolic SAR se move com maior aceleração e pode mudar de posição em relação ao preço. Em uma tendência de alta (Up Trend), o indicador fica abaixo dos preços, em uma tendência de baixa (Down Trend), fica acima.

Se o preço cruza a linha do Parabolic SAR, ocorre a reversão do indicador, e os próximos valores passam a ficar do outro lado do preço. Nesse ponto de "reversão", é usada como referência a máxima ou mínima do período anterior. A reversão do indicador é um sinal de que a tendência terminou (ou entrou em correção ou lateralização), ou de que houve inversão da tendência.

Parabolic SAR define de forma excelente os pontos de saída do mercado. Posições longas devem ser fechadas quando o preço cai abaixo da linha do indicador técnico, e posições curtas, quando o preço sobe acima da linha do Parabolic SAR. Ou seja, é necessário acompanhar a direção do movimento do Parabolic SAR e manter abertas no mercado apenas as posições que estejam na direção desse movimento. Frequentemente, esse indicador é utilizado como linha de stop móvel (trailing stop).

Se estiver aberta uma posição longa (ou seja, o preço está acima da linha do Parabolic SAR), então a linha do indicador continuará subindo, independentemente da direção dos preços. O quanto a linha do Parabolic SAR se desloca depende da magnitude do movimento de preço.

A média móvel, devido ao seu atraso natural, também é adequada para mover a linha do stop conforme o preço. Para uma posição longa, se a linha do indicador estiver abaixo do preço, é possível ajustar o stop para cima conforme o valor da média móvel. O preço cruzará a média móvel de forma natural, e o stop será acionado. Para uma posição curta, o stop deve seguir a linha da média móvel caso o preço esteja abaixo dela.

Já publiquei anteriormente um artigo sobre como integrar qualquer tipo de trailing aos EAs: "Como fazer qualquer tipo de Trailing Stop e conectar ao EA". Nele, é proposto criar classes de trailing e simplesmente conectá-las ao EA.

Vamos aproveitar essa abordagem. A única coisa que não se encaixa muito bem aqui é o fato de que, no artigo, cada símbolo ou magic number executa seu próprio laço de iteração sobre as posições abertas. Sendo assim, precisaremos modificar a classe apresentada no artigo. Felizmente, isso não é difícil.

Vamos carregar, a partir dos arquivos anexos ao artigo, o único arquivo que nos interessa: Trailings.mqh. No artigo anterior, criamos uma pasta onde colocamos os arquivos de classes e EAs para negociação baseada no histórico de ordens: \MQL5\ExpertsTradingByHistoryDeals. Salvamos o arquivo Trailings.mqh nesta pasta e o abrimos no editor de código MetaEditor.

O método principal e mais importante, chamado a partir do programa, é o método Run(). Se observarmos seu código, veremos que aqui é realizada uma iteração sobre as posições abertas:

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

Se para cada objeto de negociação por símbolo (ver o primeiro artigo) chamarmos esse método, teremos tantos laços de iteração de posições abertas quanto símbolos utilizados na negociação. Isso não é eficiente. Vamos fazer com que o próprio programa tenha um laço de iteração sobre todas as posições abertas, e que esses métodos sejam chamados a partir desse laço. Porém, o método também contém um laço embutido... Portanto, precisaremos criar na classe de trailing um outro método Run(), que aceite o ticket da posição cujo stop deverá ser ajustado, e chamaremos esse novo método a partir do laço principal.

Na classe do trailing simples, declaramos um novo método, que receberá como argumento o ticket da posição desejada:

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

Fora do corpo da classe, escreveremos sua implementação:

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

Agora, esse método poderá ser chamado tanto a partir do método Run(), que não possui parâmetros formais mas contém o laço de iteração sobre todas as posições abertas, quanto diretamente pelo programa, passando-se o ticket necessário.

Vamos ajustar o método Run() sem parâmetros formais. Removeremos do laço o bloco de código:

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

Em seu lugar, incluiremos a chamada do novo 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;
  }

Na classe de trailing baseada em valor específico, declaramos da mesma forma o novo método Run() com o argumento do ticket da posição:

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

E fora do corpo da classe, escreveremos sua implementação:

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

Aqui, é chamado o método Run() com o ticket da posição, que foi adicionado à classe de trailing simples.

É recomendável se familiarizar com as classes de trailing através do artigo, do qual extraímos o arquivo com os trailings.

A classe de negociação por símbolo, criada no artigo anterior, armazena as listas de ordens e a classe de negociação CTrade da Biblioteca Padrão. Para não alterá-la diretamente ao conectar as classes de trailing, vamos criar uma nova classe baseada nela, onde adicionaremos o controle dos trailings.

No diretório do terminal \MQL5\ExpertsTradingByHistoryDeals\ criaremos um novo arquivo SymbolTradeExt.mqh para a classe CSymbolTradeExt. Ao arquivo, devem ser conectados o arquivo das classes de trailing e o arquivo da classe CSymbolTrade, da qual nossa nova classe será herdada:

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

Ao usar a classe de trailing tirada do artigo, teremos à disposição trailings baseados no Parabolic e em todos os tipos padrão de médias móveis. A classe também possui um trailing por valores informados manualmente, mas este não será usado aqui, pois exige que os níveis de stop sejam calculados diretamente no programa e passados como parâmetros ao método Run() da classe de trailing. Um exemplo seria calcular os níveis com base no indicador ATR e passar os valores calculados para a classe de trailing.

Vamos escrever uma enumeração dos 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 que os valores das constantes da enumeração começam em 2, e não em zero? No EA que usaremos como base para o novo, também há uma enumeração de modos de teste:

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

Aqui existem duas constantes: negociação original (0) e negociação com valores definidos para ordens de stop (1). Mais adiante, adicionaremos novas constantes aqui, correspondentes às constantes da enumeração de trailing. Por isso, os valores das constantes do enum de trailing começam em 2.

Na seção privada da classe, vamos declarar um ponteiro para o objeto da classe de trailing e uma variável para armazenar o período do gráfico, que será usado no cálculo dos indicadores utilizados nos trailings. Na seção pública, declararemos o método para configurar os parâmetros do trailing, o método que inicia o trailing das posições, além dos construtores e do destrutor:

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

No construtor da classe, na linha de inicialização, o símbolo para o qual será criado o objeto é passado para o construtor da classe pai, o valor do timeframe para cálculo do indicador recebe o valor fornecido nos parâmetros formais, e o ponteiro para o objeto da classe de trailing é inicializado com NULL:

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

No destrutor da classe, caso o objeto de trailing tenha sido criado, ele é deletado:

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

Como diferentes tipos de trailing, baseados em diferentes tipos de indicadores, possuem parâmetros distintos entre si, todos esses parâmetros serão passados para o método de configuração de trailing por meio da estrutura MqlParam. Para cada tipo de trailing, o conjunto de parâmetros será diferente, e cada campo da estrutura conterá o valor do parâmetro correspondente ao indicador específico usado.

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

Ao chamar esse método para diferentes tipos de trailing, é importante preencher corretamente a estrutura MqlParam. Isso será tratado quando ajustarmos o EA para o uso com trailing.

Método que inicia o trailing da posição indicada por seu 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);
  }

A partir do EA, em seu laço sobre as posições abertas, dependendo do símbolo da posição, obteremos da lista o objeto de negociação do símbolo, e a partir dele chamaremos esse método para aplicar o trailing stop à posição, cujo ticket será passado como argumento. Se o objeto de trailing existir, seu novo método Run() será chamado com o ticket da posição.


Testando diferentes tipos de TrailingStop

Para o teste, vamos utilizar o arquivo do EA TradingByHistoryDeals_SLTP.mq5 da matéria anterior e salvá-lo na mesma pasta \MQL5\ExpertsTradingByHistoryDeals\ com um novo nome: TradingByHistoryDeals_Ext.mq5.

A inclusão do arquivo da classe CSymbolTrade será substituída pela inclusão do arquivo da classe CSymbolTradeExt, incluiremos também o arquivo das classes de trailing e ampliaremos a lista de constantes da enumeração dos modos de teste, adicionando a opção de escolha do tipo de trailing desejado:

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

Nos parâmetros de entrada do EA, adicionaremos variáveis para configuração dos parâmetros do 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


InpMAPeriod está definido como zero por padrão. Isso se deve ao fato de que cada tipo de média móvel possui um valor de período padrão específico. Como, ao receber o valor zero, a classe de trailing passa para o indicador o valor padrão correspondente, todos os dados serão válidos caso desejemos usar os valores padrão simples para o indicador utilizado no trailing.

Em todo o código, substituímos todas as ocorrências da string "CSymbolTrade" por "CSymbolTradeExt". Agora temos uma nova classe de objeto de negociação por símbolo, CSymbolTradeExt, herdada da antiga classe CSymbolTrade, escrita no artigo anterior. E nessa nova classe, foi declarado um objeto da classe de trailing. Por isso substituímos o tipo da antiga classe pela nova. Na prática, não seria necessário fazer essa substituição em todos os lugares, apenas onde for necessário usar trailing. Mas, para simplificar, não entraremos aqui em detalhes sobre herança de classes e apenas faremos a substituição completa.

Os trailings serão acionados para cada posição, com base em seu ticket. E para acessar o trailing de uma posição, precisamos obter da lista o objeto de negociação correspondente ao símbolo no qual a posição está aberta. Para isso, escreveremos uma função que retorna o ponteiro para o objeto de negociação a partir do nome do 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 função recebe como argumento o nome do símbolo para o qual deve ser retornado o objeto de negociação, além do ponteiro para a lista que contém os ponteiros para os objetos de negociação dos símbolos. No objeto de negociação temporário, é atribuído o nome do símbolo passado para a função, e em seguida o índice do objeto com esse nome é buscado na lista. No final, o ponteiro para o objeto procurado é retornado com base nesse índice. Caso o objeto não esteja presente na lista, o índice será igual a -1, e será retornado NULL.

Durante a criação do array de símbolos utilizados, os objetos de negociação dos símbolos são criados, e é dentro desses objetos que os trailings definidos nos parâmetros de entrada devem ser inicializados. Vamos ajustar a função que cria o 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;
  }

Aqui, adicionamos a declaração da estrutura de parâmetros de entrada do indicador e um pequeno bloco de código, onde o trailing é inicializado no objeto de negociação.

Para configurar os parâmetros do tipo de trailing escolhido nas configurações, vamos escrever uma função 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;
     }
  }

Nessa função, para cada tipo de trailing, são atribuídos à estrutura os valores dos indicadores utilizados no trailing, com base nos parâmetros de entrada do EA. Os valores atribuídos aos campos da estrutura são verificados quanto à sua validade e, se necessário, são ajustados na nova classe CSymbolTradeExt, a classe do objeto de negociação por símbolo que já analisamos acima.

Vamos aprimorar a função de negociação baseada no histórico de ordens. Agora precisamos distinguir claramente qual tipo de teste está sendo utilizado.
Para o teste da negociação original, não é necessário definir ordens de stop
, pois todas as posições são encerradas com base nas ordens de fechamento do histórico.

Se o teste for com tamanhos diferentes de ordens de stop, será necessário obter os tamanhos corretos e aplicá-los à posição aberta. No caso dos testes com trailings, não é obrigatório definir os stops, mas, para evitar grandes rebaixamentos e "alongamentos" indesejados, é recomendado definir stops iniciais, que depois serão ajustados automaticamente pelo trailing, conforme a lógica definida. Nas configurações do EA, existem parâmetros que determinam se esses stops iniciais devem ou não ser aplicados:

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

Ao usar esses modos, também é necessário obter os tamanhos corretos dos stops e aplicá-los à posição que será aberta:

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

Agora vamos escrever a função responsável por executar o trailing das posições:

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

Aqui não há mistério: percorremos cada posição em um laço, verificamos se o magic number e o símbolo correspondem aos definidos nas configurações do EA e, se a posição for válida, obtemos o objeto de negociação do símbolo da posição e, a partir dele, chamamos o método de trailing, informando o ticket da posição.

Vamos inserir a chamada dessa função no manipulador OnTick() do EA:

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

Pronto. O EA está finalizado. O código completo do EA pode ser consultado nos arquivos anexados ao artigo.

Vamos testar os diferentes tipos de trailing e comparar os resultados da negociação original com os da negociação usando trailing stop com diferentes algoritmos.

Ao iniciar o EA no gráfico de um símbolo, ele coleta o histórico de negociações, grava em um arquivo e exibe um alerta indicando as configurações ideais para o testador — data de início dos testes, depósito inicial e alavancagem:

No log, ele exibe os símbolos utilizados na negociação e a quantidade de ordens executadas em 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

Agora, vamos executar o EA no testador com os parâmetros recomendados, conforme informados no alerta e repetidos no log.

Negociação original:

Observamos que a negociação original gerou um prejuízo de 658 dólares.

Vamos testar os diferentes trailings. Permitiremos a definição de um stop inicial com tamanho de 100 pontos, e executaremos cada tipo de trailing no testador.
Um por vez, sem alterar outros parâmetros, para ver como a negociação se comporta.

Trailing simples:

Registrou prejuízo de 746,1 dólares. Ainda maior que na negociação original.

Vamos verificar os trailings com base em indicadores.

Trailing com Parabolic SAR:

Lucro - 541,8 dólares.

Agora analisaremos trailings baseados em diferentes tipos de médias móveis.

Trailing com AMA:

Lucro - 806,5 dólares.

Trailing com DEMA:

Lucro de 1397,1 dólares.

Trailing com FRAMA:

Lucro de 1291,6 dólares.

Trailing com MA:

Lucro - 563,1 dólares.

Trailing com TEMA:

Lucro - 1355,1 dólares.

Trailing com VIDYA:

Lucro - 283,3 dólares.

Assim, a tabela final mostra os resultados obtidos com o uso de diferentes tipos de trailing StopLoss em comparação com a negociação original:

#
Tipo de trailing
Tamanho do StopLoss
Tamanho do TakeProfit
 Lucro final
1
Negociação original
100
500
 - 658.0
2
TrailingStop simples
Inicialmente 100, depois stop a 100 pontos do preço
0
 - 746.1
3
TrailingStop por Parabolic SAR
Inicialmente 100, depois stop conforme o valor do indicador
0
 + 541.8
4
TrailingStop por VIDYA
Inicialmente 100, depois stop conforme o valor do indicador
0
 -283.3
5
TrailingStop por MA
Inicialmente 100, depois stop conforme o valor do indicador
0
 + 563.1
6
TrailingStop por AMA
Inicialmente 100, depois stop conforme o valor do indicador
0
 + 806.5
7
TrailingStop por FRAMA
Inicialmente 100, depois stop conforme o valor do indicador
0
 + 1291.6
8
TrailingStop por TEMA
Inicialmente 100, depois stop conforme o valor do indicador
0
 + 1355.1
9
TrailingStop por DEMA
Inicialmente 100, depois stop conforme o valor do indicador
0
 + 1397.1

Como resultado, quando a negociação original foi negativa, o trailing simples — um equivalente aproximado ao trailing padrão do terminal cliente — gerou ainda mais prejuízo. Vamos analisar o trailing baseado no Parabolic SAR, apresentado como um indicador que mostra reversões e níveis de stop (Stop And Reverse). Sim, ao seguir o nível de StopLoss conforme a linha do indicador no primeiro candle, obtivemos um lucro de 540 dólares.

Agora, vamos observar os resultados ao mover os stops das posições com base nos valores de diferentes tipos de médias móveis no primeiro candle. O trailing com o indicador VIDYA teve prejuízo de 280 dólares, mas todos os outros apresentaram lucro. Inclusive, seguir o stop com a média móvel simples deu um lucro maior do que o trailing com Parabolic SAR. E o verdadeiro "campeão" na melhora dos resultados da negociação foi a média móvel exponencial dupla — +1397 dólares de lucro.


Considerações finais

Criamos um EA que permite testar sua negociação no testador de estratégias e escolher o trailing stop mais adequado ao seu estilo de operação. Deve-se levar em conta que todos os parâmetros dos indicadores utilizados nos trailings durante os testes estavam com valores padrão, e as configurações dos trailings foram escolhidas de forma aleatória, "no olhômetro".

A abordagem mais correta seria realizar testes individualizados para cada símbolo, e ajustar as configurações dos trailings levando em conta as particularidades de movimento e volatilidade de cada par de moedas. No entanto, este EA ainda não oferece configurações separadas para definir "seus" próprios parâmetros para cada símbolo negociado individualmente. Mas o mais importante foi demonstrar que, com o testador de estratégias, é possível testar, analisar e melhorar sua negociação. E isso significa que o EA pode ser aprimorado para permitir a definição de parâmetros personalizados por símbolo — e isso não é difícil. O principal é ter vontade.

Estão anexados ao artigo todos os arquivos das classes e do EA discutidos. Também está incluído um arquivo compactado que, ao ser extraído, já traz os arquivos prontos para teste nas pastas corretas do terminal.

Programas utilizados no artigo:

#
Nome
 Tipo  Descrição
1
Trailings.mqh
Biblioteca de classe
Biblioteca de classes de trailing
2
SymbolTrade.mqh
Biblioteca de classe
Biblioteca de estrutura e classe de negociação, classe de negociação por símbolo
3
SymbolTradeExt.mqh
Biblioteca de classe
Classe de negociação por símbolo com trailing
4
TradingByHistoryDeals_Ext.mq5
Expert Advisor
EA para visualização e modificação no testador de estratégias de ordens e negociações executadas na conta, com uso de StopLoss, TakeProfit e trailings
5
MQL5.zip
Arquivo compactado
Arquivo compactado com os arquivos acima, pronto para extração na pasta MQL5 do terminal cliente

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/16991

Arquivos anexados |
Trailings.mqh (101.41 KB)
SymbolTrade.mqh (53.86 KB)
SymbolTradeExt.mqh (12.95 KB)
MQL5.zip (26.49 KB)
Últimos Comentários | Ir para discussão (4)
Roman Shiredchenko
Roman Shiredchenko | 30 jan. 2025 em 15:38
Muito obrigado pelo artigo. Ele é lido em um só fôlego e tudo fica claro de uma só vez.
Usarei os fragmentos de código e f -- ii em meus robôs de lances. Também usarei o trawl simples.
Um em alguns robôs e outro trawl em outros.
Yevgeniy Koshtenko
Yevgeniy Koshtenko | 30 jan. 2025 em 23:21
Artigo TOP!
den2008224
den2008224 | 31 jan. 2025 em 05:16

Трал по VIDYA:

Lucro - 283,3 dólares.

Erro: Perda - 283,3 dólares.

Alexey Viktorov
Alexey Viktorov | 31 jan. 2025 em 06:46
den2008224 #:

Erro: Perda - US$ 283,3.

No artigo, o valor negativo do lucro está escrito.

Entretanto, o espaço após o sinal de menos foi inserido acidentalmente.
Simulação de mercado: Iniciando o SQL no MQL5 (V) Simulação de mercado: Iniciando o SQL no MQL5 (V)
No artigo anterior mostrei como você deveria proceder, a fim de conseguir adicionar o mecanismo de pesquisa. Isto para que dentro do código MQL5, você pudesse de fato fazer uso pleno do SQL. A fim de conseguir obter os resultados quando for usar o comando SELECT FROM do SQL. Mas ficou faltando falar da última função que precisamos implementar. Esta é a função DatabaseReadBind. E como para entender ela adequadamente é algo que exigirá um pouco mais de explicações. Ficou decidido que isto seria feito, não naquele artigo anterior, mas sim neste daqui. Já que o assunto é bem extenso.
Redes neurais em trading: Aprendizado dependente de contexto com memória (Conclusão) Redes neurais em trading: Aprendizado dependente de contexto com memória (Conclusão)
Estamos finalizando a implementação do framework MacroHFT para trading de alta frequência com criptomoedas, que utiliza aprendizado por reforço dependente de contexto e memória para se adaptar às condições dinâmicas do mercado. E para concluir este artigo, será realizado um teste com os métodos implementados utilizando dados históricos reais, a fim de avaliar sua eficácia.
Do básico ao intermediário: Eventos em Objetos (II) Do básico ao intermediário: Eventos em Objetos (II)
Neste artigo iremos ver como funciona os três últimos tipos de eventos que podem ser disparados por um objeto. Entender isto será algo muito divertido. Já que no final faremos algo que para muitos pode parecer um tanto quanto insanidade. Porém que é perfeitamente possível de ser feito, e tem um resultado bastante surpreendente.
Usando PSAR, Heiken Ashi e Aprendizado Profundo Juntos para Operações de Trading Usando PSAR, Heiken Ashi e Aprendizado Profundo Juntos para Operações de Trading
Este projeto explora a fusão entre aprendizado profundo e análise técnica para testar estratégias de trading no mercado de câmbio (forex). Um script em Python é usado para experimentação rápida, utilizando um modelo ONNX juntamente com indicadores tradicionais como PSAR, SMA e RSI para prever movimentos do par EUR/USD. Um script em MetaTrader 5 então leva essa estratégia para um ambiente ao vivo, usando dados históricos e análise técnica para tomar decisões de trading mais informadas. Os resultados do backtesting indicam uma abordagem cautelosa, porém consistente, com foco em gestão de risco e crescimento estável em vez da busca agressiva por lucros.