English Русский
preview
Como criar qualquer tipo de Trailing Stop e anexá-lo ao EA

Como criar qualquer tipo de Trailing Stop e anexá-lo ao EA

MetaTrader 5Exemplos | 10 outubro 2024, 10:25
36 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Introdução

Dando continuidade ao tema do trailing stop, iniciado no artigo anterior, hoje vamos analisar classes de trailing para facilitar a criação de vários algoritmos de trailing do StopLoss de posições. Com base nas classes criadas, será possível desenvolver qualquer algoritmo de ajuste de stops: por deslocamento do stop em relação ao preço atual, por indicadores, por níveis de StopLoss especificados, etc. Após a leitura do artigo, poderemos criar e anexar a qualquer EA qualquer algoritmo de ajuste de stops das posições. Além disso, a conexão e o uso do trailing serão fáceis e claros.

Vamos rever brevemente o algoritmo de funcionamento de um trailing stop. Vamos estabelecer que, para cada trailing, podem ser usadas três condições de operação:

  • início do trailing — número de pontos de lucro da posição, ao atingir o qual o trailing stop é ativado;
  • passo do trailing — número de pontos que o preço deve percorrer na direção do lucro da posição para o próximo ajuste do StopLoss da posição;
  • distância do trailing — distância em relação ao preço atual na qual o StopLoss é mantido.

Esses três parâmetros podem ser aplicados a qualquer trailing. Qualquer um desses parâmetros pode estar presente nas configurações do trailing ou ausente, se não for necessário ou substituído por algum valor no algoritmo de trailing. Um exemplo de substituição do parâmetro "distância do trailing" pode ser o valor do indicador, onde o stop-loss da posição é definido. Nesse caso, se esse parâmetro for ativado, o stop será definido não no preço indicado pelo indicador, mas com um desvio do preço indicado no valor da distância em pontos.

Em geral, esses três parâmetros são os mais usados em diferentes trailing stops, e os consideraremos ao criar as classes de trailing.

Ao mover o stop-loss da posição para o preço exigido, devem ser feitas verificações:

  • o preço do StopLoss não deve estar mais próximo do preço atual do que o nível de StopLevel do símbolo (SYMBOL_TRADE_STOPS_LEVEL — desvio mínimo em pontos do preço atual de fechamento para definir ordens Stop);
  • o preço do StopLoss não deve ser igual ao já definido, e deve ser maior que o nível atual do stop-loss para uma posição longa e menor para uma posição curta;
  • se os parâmetros mencionados acima forem usados no algoritmo de trailing, também será necessário verificar as condições estabelecidas por esses parâmetros.

Essas são verificações básicas. Qualquer algoritmo de trailing funciona da mesma forma: o preço necessário para definir o stop é passado como entrada, todas as verificações necessárias são feitas e, se forem aprovadas, o stop da posição é movido para o nível indicado.

Um trailing simples pode funcionar assim:

  1. no parâmetro "início do trailing", o lucro em pontos é especificado, ao atingir o qual o trailing deve ser ativado, ou zero, se esse parâmetro não for usado;
  2. no parâmetro "passo do trailing", é indicado quantos pontos de lucro devem ser percorridos para ajustar o stop atrás do preço, ou zero, se esse parâmetro não for usado;
  3. no parâmetro "distância do trailing", é definida a distância entre o preço atual e o stop da posição, na qual o stop deve ser mantido, ou zero, se esse parâmetro não for usado;
  4. também é possível definir o símbolo e o magic das posições que precisam ser trailadas, ou NULL e -1, respectivamente, se todas as posições de todos os símbolos e com qualquer magic precisarem ser trailadas.

Ao atingir o número de pontos de lucro da posição especificado, o trailing é iniciado, e o stop da posição é definido a uma distância especificada do preço atual. Em seguida, depois que o preço percorre o número de pontos indicado em direção ao lucro da posição (passo do trailing), o stop da posição é novamente ajustado para manter a distância especificada. E assim continuamente até que o preço se mova contra a posição. Nesse caso, o stop permanece no nível já definido. Assim que o preço atinge esse nível, a posição é fechada com lucro pelo StopLoss. Assim, o trailing stop permite manter o lucro, fechando a posição quando o preço inverte, e ao mesmo tempo permitindo que o preço "se mova" dentro do desvio de stop especificado, ajustando o stop passo a passo atrás do preço, se ele se mover na direção da posição.

Em vez do parâmetro "distância do trailing", pode ser especificado o valor de um preço obtido, por exemplo, de um indicador. Nesse caso, obtemos um trailing pelo indicador. Pode-se passar o valor de uma das velas anteriores (High, Low, por exemplo) — assim obtemos um trailing pelos preços das barras, e assim por diante. Mas o trailing básico — seu algoritmo — permanece o mesmo.

Portanto, para criar qualquer algoritmo de trailing stop, primeiro precisamos criar um trailing simples, que permita passar para ele os preços necessários para os stops das posições, e mova o StopLoss da posição para esse nível com todas as verificações necessárias.

No artigo anterior, já analisamos as funções anexáveis, onde havia um trailing simples e trailing por vários indicadores. Essa abordagem permite anexar o trailing stop ao EA para trailing das posições do símbolo atual. Mas há também limitações: para cada trailing por indicador, no próprio EA, é necessário criar o indicador e enviar seu handle para a função de trailing. As próprias funções de trailing podem trabalhar apenas com as posições do símbolo atual.

Se usarmos classes de trailing, podemos criar várias instâncias de uma classe de trailing com diferentes configurações, e então, em um único EA, todos os trailings criados dessa forma poderão funcionar simultaneamente, com um algoritmo específico definido pelo programador. Ou seja, para cada conjunto de parâmetros, praticamente "com algumas linhas de código", será criado um trailing próprio, que funcionará com seu próprio algoritmo. Cada trailing criado pode ser executado no EA sob certas condições, criando algoritmos complexos de trailing de StopLoss das posições.

Mesmo que se criem dois trailings idênticos, mas para símbolos diferentes, cada símbolo terá seu próprio trailing, que funcionará com os dados daquele símbolo especificado ao criar o trailing. Isso simplifica bastante o uso de trailing stops em seus programas: basta criar um trailing com os parâmetros necessários e chamá-lo nos manipuladores do EA. Justiça seja feita, deve-se notar que essa abordagem, em alguns algoritmos, pode ser subótima, pois para cada tipo de trailing é realizada uma verificação separada das posições existentes no terminal. Se quisermos desenvolver um algoritmo realmente universal, que utilize a mesma lista de posições do terminal para cada trailing, pode acontecer que os custos de seu projeto e desenvolvimento não sejam comparáveis à sua demanda. Além disso, isso foge ao escopo deste artigo.

Portanto, para começar, precisamos escrever a base: uma classe simples de trailing stop que mova o stop-loss das posições para o preço especificado, com todas as verificações necessárias.

No artigo anterior, esse trailing simples básico foi chamado de TrailingStopByValue(). Aqui, o chamaremos de SimpleTrailing — "Trailing Simples". E ele poderá ser totalmente utilizado em seus programas.

Como no artigo anterior, todas as classes de trailing serão organizadas em um único arquivo. Esse arquivo é simplesmente anexado ao EA e utilizado. Normalmente, os arquivos anexáveis são armazenados na pasta \MQL5\Include\ ou em uma subpasta deste diretório.


Classe base de trailing

Na pasta do terminal \MQL5\Include\, criaremos uma nova subpasta Trailings\, e nela, um novo arquivo de classe com o nome Trailings.mqh:



O nome da classe deve ser CSimpleTrailing, o nome do arquivo criado será Trailings.mqh, e a classe base será a classe base de objeto da Biblioteca Padrão, CObject:


Após a conclusão do assistente MQL, obtemos a seguinte estrutura da classe na pasta \MQL5\Include\Trailings\Trailings.mqh:

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

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


Anexamos o arquivo do objeto base da Biblioteca Padrão ao arquivo criado:

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

#include <Object.mqh>

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

Por que é necessário herdar todas as classes de trailing criadas do objeto base da Biblioteca Padrão?

Isso permitirá criar facilmente coleções de trailing a partir dessas classes — colocá-las em listas, procurar os objetos necessários nas listas, etc. Ou seja, se quisermos apenas usar futuros objetos de classes como instâncias em arquivos de EA, não é necessário herdar de CObject. Mas se quisermos criar coleções completas a partir dessas classes e usar todas as funcionalidades da Biblioteca Padrão, essas classes devem ser herdeiras da classe base CObject:

A classe CObject é a classe base para a construção da biblioteca padrão do MQL5 e oferece a todos os seus descendentes a capacidade de ser um elemento de uma lista encadeada.
Além disso, vários métodos virtuais são definidos para implementação futura nas classes derivadas.

Na seção privada da classe, declararemos três métodos:

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

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

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

protected: 

O método CheckCriterion() retornará uma flag indicando que todas as verificações necessárias para a modificação do StopLoss da posição foram aprovadas (ou não).

O método ModifySL() modificará o stop-loss da posição de acordo com o valor especificado.

O método StopLevel() retornará o valor da propriedade StopLevel do símbolo.

Todos esses métodos, no artigo anterior, eram funções separadas. Agora, eles trabalharão dentro da classe de trailing base, da qual herdaremos outras classes de trailing.

Na seção protegida da classe, declararemos todas as variáveis necessárias para o funcionamento da classe:

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:

Das variáveis declaradas aqui, apenas o "multiplicador do spread" exige explicação. O valor StopLevel do símbolo é a distância até o preço atual, abaixo da qual não é permitido definir um stop-loss. Se o valor StopLevel estiver definido como zero no servidor, isso não significa a ausência de um StopLevel. Isso significa que o nível de stop é flutuante e geralmente equivale a dois spreads. Às vezes, três. Depende das configurações do servidor. Por isso, será possível definir esse multiplicador manualmente aqui. Se for utilizado o spread duplo para o valor StopLevel, o valor da variável m_spread_mlt será igual a dois (por padrão). Se for o spread triplo, então três, e assim por diante.

O método virtual GetStopLossValue() calcula e retorna o preço StopLoss para a posição. Em diferentes tipos de trailing stops, o nível do stop-loss para o qual o stop da posição deve ser definido é calculado de maneira diferente. Por essa razão, esse método é declarado como virtual, e deve ser substituído em cada classe derivada de trailing se o cálculo do stop-loss for diferente do que será escrito neste método desta classe.

Na seção pública da classe, escreveremos métodos para definir e retornar os valores das variáveis declaradas na seção protegida da classe, além dos construtores e do destrutor da classe:

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

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

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

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


A classe terá dois construtores:

  • O primeiro construtor será padrão, onde o símbolo utilizado será o atual, o magic será definido como -1 (para todas as posições do símbolo atual), e os parâmetros do trailing serão definidos como zero.
  • O segundo construtor será paramétrico, onde em seus parâmetros formais serão passados o nome do símbolo, o valor do magic das posições e os três parâmetros do trailing — início, passo e distância.

Vejamos a implementação do construtor paramétrico:

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

Ao contrário do construtor padrão, onde na linha de inicialização todos os valores de variáveis são definidos para o símbolo atual e valores de trailing zerados, aqui os valores são passados para o construtor e atribuídos às variáveis correspondentes. O método SetSymbol() define os valores de várias variáveis de uma vez:

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

Em ambos os construtores, o multiplicador de spread é definido como 2. Se necessário, ele pode ser alterado pelo método SetSpreadMultiplier().


Método virtual que calcula e retorna o nível de StopLoss da posição selecionada:

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

Como este é um trailing simples, aqui o stop-loss da posição será sempre mantido a uma distância especificada do preço atual.

Os valores de StopLoss calculados para as posições são retornados pelo método e verificados em seguida no método CheckCriterion():

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

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

Se o valor de StopLoss passado para o método atender a todos os critérios de todos os filtros, o método retornará true, e o stop-loss da posição será modificado usando o método ModifySL():

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


Para obter o nível de stop, usamos o método StopLevel():

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

Como o nível de stop é uma variável não constante e pode mudar dinamicamente, cada vez que chamamos o método, solicitamos os valores necessários das propriedades do símbolo. Se o StopLevel do símbolo estiver definido como zero, usamos o valor do spread do símbolo multiplicado por 2. Esse valor é definido por padrão.

Todos os três métodos mencionados anteriormente já foram implementados como funções separadas no artigo anterior. Agora, esses métodos estão ocultos na seção privada da classe e executam suas funções sem acesso externo a eles.

O principal método do trailing é o método Run(), que inicia o ciclo de seleção de posições e move seus stops para os valores calculados:

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

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

O método percorre as posições na lista de posições ativas do terminal, filtra-as por símbolo e magic, e move os stops das posições para a distância calculada a partir do preço atual. O resultado da modificação do StopLoss de cada posição é adicionado à variável res. Ao final do ciclo de modificação dos stops de todas as posições, a variável conterá o valor true — apenas se a modificação dos stops de todas as posições filtradas por símbolo/magic for bem-sucedida. Caso contrário, se ao menos uma posição não for modificada com sucesso, o valor será false. Isso é feito para fornecer controle adicional sobre a modificação dos stops externamente.

Agora, vamos anexar essa classe ao EA para verificar seu funcionamento.

Para o teste, vamos usar o EA Moving Average.mq5 da pasta \MQL5\Experts\Examples\ do terminal e salvá-lo com o nome MovingAverageWithSimpleTrail.mq5.

Anexamos o arquivo da classe de trailing ao EA, inserimos parâmetros adicionais nas configurações e criamos uma instância da classe SimpleTrailing:

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

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

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

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

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

CSimpleTrailing ExtTrailing;              // simple trailing class instance

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


No manipulador OnInit(), definimos os parâmetros do trailing:

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


No manipulador OnTick(), iniciamos o trailing:

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


Isso é tudo o que precisa ser adicionado ao arquivo do EA. Vamos compilá-lo e executá-lo no testador de estratégias no gráfico EURUSD, M15, em um teste individual do último ano. Todos os ticks sem atrasos de execução.

Os parâmetros serão definidos como segue (trailing desativado):


Executamos o teste com o trailing desativado e observamos o resultado:



Agora, ativamos o uso do trailing:


e executamos o mesmo teste novamente.

O resultado do teste foi o seguinte:



O resultado foi um pouco melhor. Percebe-se que o trailing melhora um pouco as estatísticas com essas configurações.

Agora, com base no trailing simples criado, podemos desenvolver qualquer outro tipo de trailing.

Vamos criar algumas classes de trailing por indicadores — pelo indicador Parabolic SAR e por diferentes médias móveis da lista de indicadores de tendência disponíveis no terminal do cliente.


Classes de trailing por indicadores

Vamos continuar escrevendo o código no mesmo arquivo. Mas antes de iniciar uma nova classe de trailing por indicador, lembremos que, para trailing baseado em valores de indicadores, é necessário criar o próprio indicador, a partir do qual obteremos os valores dos níveis de stop-loss. Para os indicadores, ao criá-los, é necessário especificar o símbolo e o timeframe, sobre cujos dados o indicador construirá sua série de dados. Além disso, é necessário armazenar o handle do indicador em uma variável, para podermos acessá-lo e obter os dados. A obtenção dos dados pelo handle independe do tipo de indicador — os dados são obtidos usando a função CopyBuffer() com o handle do indicador.

Todas as ações de obtenção de dados do indicador devem ser escritas nas classes de trailing por indicadores. Mas, nesse caso, seria necessário repetir essas ações em cada classe de trailing para diferentes indicadores. Isso é possível, mas não é ideal. É melhor criar uma classe base de trailing por indicadores, onde os métodos para obter dados do indicador pelo handle serão implementados, e algumas variáveis comuns a todos os indicadores (símbolo, timeframe, índice da série de dados e handle do indicador) serão definidas. As classes de trailing seriam herdadas dessa classe base. Assim, a estrutura da classe ficaria organizada, sendo que a classe base gerencia o acesso aos dados do indicador pelo handle, enquanto as classes herdadas criam o indicador necessário e trabalham com seus dados.

Vamos continuar escrevendo o código no arquivo Trailings.mqh. Vamos escrever a classe base de trailing por indicadores:

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

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

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

Essa classe herda da classe SimpleTrailing, portanto, tem disponível toda a funcionalidade dessa classe, além dos métodos para acessar os dados do indicador pelo handle.

Na seção protegida da classe, estão declaradas as variáveis da classe, o método para obter dados do indicador pelo handle e um método virtual para calcular os níveis de StopLoss, que sobrescreve o método de mesmo nome da classe pai.

Na seção pública da classe, estão localizados os métodos de definição e obtenção das propriedades do indicador, além dos construtores e do destrutor.

No construtor padrão, na linha de inicialização, o símbolo atual, o magic com valor -1 (todas as posições) e parâmetros de trailing zerados são passados para a classe pai. O timeframe do gráfico atual é definido como o período para o cálculo do indicador.

No construtor paramétrico, o símbolo e o período do gráfico para o cálculo do indicador, assim como todos os parâmetros do trailing, são passados nos parâmetros formais do construtor.
No destrutor da classe, a parte de cálculo do indicador é liberada, e a variável que armazena o handle recebe o valor INVALID_HANDLE.


Método que retorna os dados do indicador no índice da série de dados especificado:

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

Com CopyBuffer(), obtemos o valor de uma barra do indicador no índice especificado e retornamos esse valor se a obtenção for bem-sucedida. Em caso de erro, uma mensagem é exibida no log e um valor vazio (EMPTY_VALUE) é retornado.

A função CopyBuffer() funciona com os dados de qualquer indicador — pelo seu handle. Assim, esse método é universal e pode ser usado sem modificações em qualquer classe de trailing por indicadores.


Método virtual que calcula e retorna o nível de StopLoss da posição selecionada:

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

Nesse método, na classe pai, o cálculo da distância do nível de stop-loss em relação ao preço é realizado. Nesta classe, no entanto, será necessário obter os dados do indicador e usá-los como o nível de StopLoss. Se os dados do indicador não forem obtidos, o método retornará o preço atual, o que impedirá a colocação do StopLoss da posição, pois não é permitido definir o stop no preço atual devido ao nível StopLevel.

Como resultado, essa classe implementa o acesso aos dados do indicador via seu handle e o cálculo do nível para o StopLoss da posição com base no valor do indicador, e todos os seus métodos estarão disponíveis nas classes derivadas.

Agora, podemos criar classes de trailing com base no indicador utilizando essa classe base.


Classe de trailing por Parabolic SAR

Vamos continuar escrevendo o código no mesmo arquivo \MQL5\Include\Trailings\Trailings.mqh.

A classe de trailing por Parabolic SAR, como todas as outras classes de trailing baseadas em indicadores, deve ser herdada da classe base de trailing por indicadores.

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

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

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

Na seção privada da classe, são declaradas variáveis para armazenar os valores dos parâmetros do Parabolic SAR.

Na seção pública, estão os métodos para definir e retornar os valores das propriedades do indicador, um método para criar o indicador e dois construtores — o padrão e o paramétrico.

No construtor padrão, o indicador é criado com base no símbolo e no período do gráfico atual, e os parâmetros do trailing são definidos como zero.

No construtor paramétrico, todos os parâmetros são passados pelas variáveis de entrada do construtor e, em seguida, o indicador é criado com os parâmetros fornecidos:

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


O método Initialize() cria o Parabolic SAR e retorna o resultado da sua criação:

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


Vamos testar a classe de trailing por Parabolic SAR criada.

Para o teste, vamos usar o EA da distribuição padrão \MQL5\Experts\Advisors\ExpertMACD.mq5, salvá-lo com um novo nome ExpertMACDWithTrailingBySAR.mq5 e adicionar as linhas de código necessárias.

Anexamos o arquivo com as classes de trailing, adicionamos novas variáveis de entrada e declaramos uma instância da classe de trailing:

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

#include <Trailings\Trailings.mqh>

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

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

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


No manipulador OnInit(), inicializamos o trailing pelo Parabolic SAR:

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


No manipulador OnTick(), iniciamos o trailing:

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


Compilamos o EA e o executamos no testador com os parâmetros padrão:



Vemos que os stops das posições estão sendo ajustados corretamente conforme os valores da primeira barra do Parabolic SAR.

Agora, vamos criar classes de trailing baseadas nas médias móveis disponíveis no terminal do cliente.


Classes de trailing por médias móveis

Essas classes diferem da classe de trailing por Parabolic SAR apenas no conjunto de parâmetros de entrada e no fato de que, no método Initialize(), é criado o indicador correspondente à classe.

Com base nisso, vamos analisar apenas uma classe: a classe de trailing por média móvel adaptativa:

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

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

A classe é absolutamente idêntica à classe de trailing por Parabolic SAR discutida acima. Em cada classe de trailing por médias móveis, haverá um conjunto de variáveis para armazenar os parâmetros do indicador e os métodos correspondentes para definir e retornar os valores. Todas as classes de trailing por médias móveis são idênticas entre si, e você pode estudá-las revisando os códigos no arquivo de classes de trailing anexado, Trailings.mqh. Para testar a classe de trailing por média móvel simples, você pode executar o EA de teste do arquivo ExpertMACDWithTrailingByMA.mq5, também anexado no final do artigo.

Criamos as classes de trailing simples e de trailing por indicadores padrão disponíveis no terminal, adequadas para definir os níveis de StopLoss das posições.

No entanto, esses não são todos os tipos de trailing que podem ser criados usando as classes apresentadas. Há trailing stops que movem os stops das posições para níveis de preços específicos, indicados separadamente para posições longas e curtas. Um exemplo desse tipo de trailing seria o trailing pelos valores de High/Low das velas ou, por exemplo, pelo indicador de fractais.

Para implementar esse trailing, precisamos da capacidade de especificar separadamente o nível de StopLoss para cada tipo de posição. Vamos criar tal classe.


Classe de trailing por níveis de StopLoss especificados

Tudo o que é necessário para criar um trailing com base em valores especificados é, em vez de parâmetros de indicador, definir variáveis para armazenar os valores de StopLoss para posições longas e curtas. Em vez de obter dados do indicador, precisamos calcular os valores do StopLoss das posições com base nos valores dessas variáveis. Os valores devem ser inseridos nas variáveis pelo programa controlador, diretamente ao chamar o método Run() do trailing.

Vamos escrever essa classe:

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

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

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

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

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

Percebe-se que a classe é praticamente idêntica a todas as classes de trailing por indicadores discutidas anteriormente. No entanto, aqui não há o método Initialize(), pois não é necessário criar nenhum indicador.

No método virtual que calcula e retorna o nível de StopLoss da posição selecionada, os valores para StopLoss são calculados a partir dos níveis estabelecidos para as posições longas e curtas:

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


No método Run(), os valores de StopLoss para posições longas e curtas são passados para a classe através de seus parâmetros formais:

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

Primeiro, os valores passados são gravados nas variáveis da classe e, em seguida, o método Run() da classe pai é chamado. O método virtual GetStopLossValue(), sobrescrito nesta classe, será chamado da classe pai, e os stops das posições serão definidos para os valores calculados.


Anexando o trailing stop ao EA

Como anexar o trailing ao EA já foi discutido acima, durante os testes dos trailing stops por indicadores. Agora, vamos ver como anexar e executar um trailing stop baseado nos valores passados, especificamente pelos valores High e Low das velas.

O trailing será anexado ao EA da distribuição padrão \MQL5\Experts\Advisors\ExpertMACD.mq5. Vamos salvá-lo com um novo nome ExpertMACDWithTrailingByValue.mq5 e fazer as modificações necessárias.

Anexamos o arquivo com as classes de trailing ao EA, adicionamos variáveis de entrada para configurar o trailing e declaramos uma instância da classe de trailing por valores especificados:

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

#include <Trailings\Trailings.mqh>

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

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

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

A variável de entrada Data Rates Timeframe é o período do gráfico a partir do qual serão extraídos os preços High para stops de posições curtas e Low para stops de posições longas.

A variável de entrada Data Rates Index for StopLoss é o índice da barra no gráfico com o período Data Rates Timeframe, a partir do qual os preços High e Low serão usados para definir os StopLoss das posições.


No manipulador OnInit() do EA, inicializamos os parâmetros do trailing:

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

Serão trailadas apenas as posições cujos magics correspondem ao magic do EA, ou seja, trailaremos apenas as posições abertas por este EA.


No manipulador OnTick() do EA, obtemos os dados da barra com o índice especificado na variável de entrada InpDataRatesIndex, e iniciamos o trailing especificando os preços de StopLoss (High e Low da barra) para posições longas e curtas:

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

Isso é tudo o que precisa ser feito para anexar o trailing ao EA. Como já deve ter sido percebido, a principal diferença ao anexar diferentes tipos de trailing a EAs está na declaração das instâncias dos diferentes tipos de trailing. Todas as outras ações para anexar os trailings são praticamente as mesmas e não devem gerar dúvidas ou dificuldades.

Vamos compilar o EA e executá-lo no modo visual do testador com as configurações padrão:

Vemos que os stops das posições são definidos nos valores de High e Low das velas com índice 2 na série temporal.

O quanto esse trailing pode melhorar o desempenho da estratégia de trading já é uma questão para testes individuais.

Além disso, qualquer pessoa pode criar seu próprio trailing stop com base em seu próprio algoritmo usando as classes apresentadas no artigo — o campo para experimentação é amplo e praticamente sem limites.


Considerações finais

Hoje, criamos classes de diferentes tipos de trailing que permitem anexar trailing stops facilmente a qualquer EA. As classes criadas também constituem um bom conjunto de ferramentas para desenvolver trailing stops com base em algoritmos próprios.

Nos exemplos, consideramos apenas uma maneira de trabalhar com objetos de classes — criando instâncias de objetos da classe de trailing. Essa abordagem permite determinar previamente, no código do programa, quais tipos de trailing serão necessários e quais parâmetros eles terão. Para cada trailing, é criado um objeto na área global. Essa abordagem é válida — simples e fácil de entender. No entanto, para criar objetos de trailing dinamicamente durante a execução do programa usando o operador new, é melhor aproveitar os recursos oferecidos pela Biblioteca Padrão para criar listas encadeadas de objetos. É para esses fins que todas as classes de trailing herdam o objeto base da Biblioteca Padrão, CObject. Esse método pode ser discutido nos comentários do artigo, pois já foge ao escopo deste tema.

Todas as classes apresentadas no artigo podem ser usadas "como estão" em seus próprios projetos ou podem ser ajustadas para atender às suas necessidades e objetivos.


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

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Algoritmo da Cauda de Cometa (Comet Tail Algorithm, CTA) Algoritmo da Cauda de Cometa (Comet Tail Algorithm, CTA)
Neste artigo, vamos explorar o novo algoritmo de otimização autoral CTA (Comet Tail Algorithm), que se inspira em objetos cósmicos únicos, nomeadamente em cometas e suas impressionantes caudas, formadas quando se aproximam do Sol. Esse algoritmo é baseado no conceito de movimento dos cometas e suas caudas, e foi projetado para encontrar soluções ótimas em problemas de otimização.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Estratégia de Bill Williams com e sem outros indicadores e previsões Estratégia de Bill Williams com e sem outros indicadores e previsões
Neste artigo, vamos dar uma olhada em uma das estratégias famosas de Bill Williams, discuti-la e tentar melhorá-la com outros indicadores e previsões.