English Русский
preview
Como adicionar Trailing Stop com o indicador Parabolic SAR

Como adicionar Trailing Stop com o indicador Parabolic SAR

MetaTrader 5Exemplos | 13 setembro 2024, 16:36
41 0
Artyom Trishkin
Artyom Trishkin

Conteúdo



Introdução

O trailing stop é uma função bem conhecida pela maioria dos traders. Essa função está integrada na plataforma MetaTrader 5 e ajusta automaticamente o nível do StopLoss, mantendo-o a uma distância definida do preço atual:

Ativando o Trailing Stop padrão no MetaTrader 5


O trailing stop é o ajuste automático do StopLoss da posição seguindo o preço, permitindo manter o stop de proteção a uma distância constante do preço. Essa abordagem permite ao trader proteger parte do lucro acumulado, sem sair da posição precocemente. Toda vez que o preço de mercado se afasta do preço de abertura da posição, o trailing stop ajusta automaticamente o StopLoss, mantendo a distância definida entre ele e o preço atual. No entanto, se o preço se aproximar do preço de abertura, o StopLoss permanece no nível anterior. Isso oferece proteção contra perdas em possíveis oscilações do mercado.

Entretanto, se você precisar de uma versão mais específica do trailing stop, sempre pode desenvolver essa função na linguagem MQL5 para aumentar as capacidades da ferramenta padrão.

Há uma função que recebe o preço necessário para definir o nível do StopLoss. O programa verifica alguns fatores impeditivos, como o nível StopLevel — a distância mínima em que os stops podem ser colocados, e o nível FreezeLevel — a distância de congelamento, dentro da qual não é possível modificar uma posição ou ordem pendente. Ou seja, se o preço se aproximar do nível de stop da posição a uma distância menor do que o nível FreezeLevel, espera-se que o stop seja acionado, e a modificação é proibida. Os trailing stops também possuem algumas configurações individuais de parâmetros, que são verificadas antes de mover o nível do stop loss para o preço indicado, como o símbolo e o magic number da posição. Todos esses critérios são verificados diretamente antes de ajustar o StopLoss da posição ao nível especificado.

Os diferentes tipos de trailing stops têm diferentes algoritmos para calcular o preço do StopLoss da posição, que são passados para a função do trailing stop e com os quais a função então trabalha.

E um dos melhores "indicadores" dos níveis necessários para o StopLoss é o indicador Parabolic SAR.



O indicador Parabolic SAR (Stop and Reverse) é uma ferramenta popular na análise técnica, usada para identificar possíveis momentos de fim e reversão de uma tendência atual. Esse indicador foi desenvolvido por Welles Wilder e é frequentemente utilizado para trailing automático de stop-loss. Aqui estão as principais razões pelas quais o indicador Parabolic SAR é atrativo para ajustar o stop de proteção:

  1. Facilidade de interpretação: O Parabolic SAR é fácil de interpretar, pois é exibido no gráfico como pontos posicionados acima ou abaixo do preço. Quando os pontos estão abaixo do preço, isso é um sinal de compra; quando os pontos estão acima, é um sinal de venda.

  2. Seguir automaticamente o preço: A principal vantagem do Parabolic SAR é sua capacidade de se adaptar automaticamente às mudanças de preço e se mover ao longo do tempo. Isso o torna uma ferramenta ideal para ajustar trailing stops, já que ele protege o lucro ao mover o stop-loss para mais perto do preço à medida que a tendência avança.

  3. Proteção de lucro: À medida que o preço se move na direção do lucro de uma posição aberta, o Parabolic SAR ajusta o nível do stop-loss, ajudando a proteger parte do lucro acumulado contra uma possível reversão da tendência.

  4. Sinais para saída: Além da função de trailing stop, o Parabolic SAR também pode servir como um sinal para fechar uma posição quando os pontos do indicador cruzam o preço. Isso pode prevenir perdas adicionais em caso de mudanças rápidas na tendência.

  5. Facilidade de configuração: Os parâmetros do Parabolic SAR (passo e máximo) podem ser facilmente ajustados para se adaptar à volatilidade específica do mercado ou à estratégia de negociação. Isso o torna uma ferramenta versátil para diferentes condições de mercado.

  6. Adequado para todos os períodos: O indicador pode ser usado de forma eficaz em vários intervalos de tempo, tornando-o adequado tanto para investidores de longo prazo quanto para traders de curto prazo.

O uso do Parabolic SAR para trailing stop é especialmente útil em mercados de tendência, onde essa ferramenta ajuda a maximizar os lucros, permitindo manter a posição aberta enquanto a tendência continuar. No entanto, é importante lembrar que, em períodos de mercado lateral ou de baixa volatilidade, o uso do Parabolic SAR pode resultar no fechamento prematuro das posições devido às mudanças frequentes na posição dos pontos do indicador.


Trailing Stop com o Parabolic SAR

Vamos analisar a estrutura de um trailing stop.

Normalmente, o código do trailing stop consiste em vários blocos autossuficientes, que podem ser destacados do esquema geral e transformados em funções:

  1. bloco de cálculo do nível necessário de StopLoss; o valor obtido é enviado para o bloco Trailing StopLoss.
  2. bloco Trailing StopLoss, que inclui:
    1. bloco de filtros
    2. bloco de ajuste do StopLoss para o valor obtido no bloco de cálculo do nível necessário de StopLoss, que inclui:
      1. bloco de filtros para verificar as condições do servidor em relação aos níveis para o símbolo e o cumprimento das condições para ajustar o StopLoss.
      2. bloco de modificação do valor do StopLoss.

O bloco de cálculo do nível necessário de StopLoss — neste caso específico, é o indicador Parabolic SAR. Seu valor, geralmente da barra 1, é enviado a cada tick para o bloco Trailing StopLoss, onde, em um loop através da lista de posições abertas, cada posição selecionada e suas propriedades passam pelo bloco de filtros — geralmente com base no símbolo e no magic number. Depois que os filtros baseados no símbolo/magic number forem aprovados, o nível necessário de StopLoss passa por uma filtragem adicional para verificar as condições do StopLevel do servidor, o passo do trailing stop, o valor do novo StopLoss em relação à sua posição anterior, e o critério de ativação do trailing com base no lucro da posição em pontos. Se esses filtros também forem aprovados, o StopLoss da posição será modificado para o novo nível.

Assim, o bloco de cálculo do nível do StopLoss já está pronto — é o indicador Parabolic SAR. Agora, é necessário criar o bloco de ajuste dos níveis de StopLoss das posições, selecionadas com base no símbolo atual e no identificador do EA (Magic Number). Se o valor do magic number for definido como -1, qualquer posição aberta no símbolo do gráfico será ajustada. Caso um magic number específico seja informado, apenas as posições correspondentes a esse magic number serão ajustadas. A função de trailing será ativada apenas na abertura de uma nova barra ou na abertura de uma nova posição. Exemplo implementado como EA

No EA, vamos criar o indicador Parabolic SAR com os parâmetros indicados nas configurações do especialista. Os valores do indicador, obtidos de uma barra específica (por padrão, da primeira), serão passados para a função de trailing stop, que será responsável por todos os cálculos necessários para ajustar os níveis de StopLoss das posições. Será necessário considerar o nível StopLevel do símbolo, abaixo do qual não é permitido definir stops. Além disso, será verificado o nível atual do StopLoss e, se o nível for igual ou superior (para compras) ou inferior (para vendas) ao valor passado para a função, o stop não será ajustado.

Essas verificações serão realizadas por uma função especial que verifica os critérios para modificar o StopLoss da posição. Somente após a conclusão de todas as verificações necessárias, o stop da posição será ajustado para o novo nível usando a função de modificação do stop.

Na pasta do terminal \MQL5Experts, criaremos um novo arquivo de EA com o nome TrailingBySAR_01.mq5.

No segundo passo do assistente de criação de novo arquivo de EA, na janela que aparece, marque a caixa para o handler OnTradeTransaction():


O handler OnTradeTransaction() será necessário para acionar o trailing stop no momento da abertura de uma nova posição.

Este handler é chamado quando ocorre o evento TradeTransactionn,, incluindo a abertura de uma nova posição.

Mais detalhes sobre transações e eventos de negociação podem ser encontrados no artigo "Por onde começar ao criar um robô de negociação para a Bolsa de Moscou MOEX".

No arquivo de EA criado, adicionaremos os seguintes parâmetros de entrada e variáveis globais:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_01.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"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

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

//--- global variables
int      ExtHandleSAR =INVALID_HANDLE// Parabolic SAR handle
double   ExtStepSAR   =0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;                 // Parabolic SAR maximum

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- 

  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {

  }


No handler OnInit() do EA, definiremos os valores corretos para os parâmetros configurados do indicador, criaremos o indicador e obteremos seu handle:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);

//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }
//--- successful
   return(INIT_SUCCEEDED);
  }

Se o indicador for criado com sucesso, um handle será gerado, o qual será usado para obter os valores do Parabolic SAR. Se houver um erro na criação do indicador, uma mensagem de erro com os dados do indicador será registrada no log. Para descrever o timeframe no qual o indicador será criado, será usada uma função que retorna a descrição textual do timeframe.

//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }

Para obter parte do texto de uma constante de timeframe, extraímos o nome do timeframe a partir da constante.
Por exemplo, da constante PERIOD_H1, extraímos a string "PERIOD_H1", e a partir dela retornamos apenas "H1".

Para verificar a abertura de uma nova barra, precisamos comparar o horário de abertura da barra atual com o horário de abertura anterior armazenado. Se os valores comparados forem diferentes, isso indica a abertura de uma nova barra.
Como estamos lidando com um EA e não com um indicador, onde já existe um array predefinido de time series para o símbolo/período atual, precisamos criar uma função para obter o horário de abertura de uma barra:

//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

Na função, passamos o índice da barra cujo horário de abertura queremos obter, copiamos os dados necessários para um array e retornamos os dados obtidos desse array. Como precisamos do horário de apenas uma barra, o array será definido com dimensão 1.

Agora, utilizando essa função, escreveremos uma função que retorna um flag indicando a abertura de uma nova barra:

//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }

Comparamos o horário de abertura anterior da barra zero com o horário atual obtido da função TimeOpenBar(). Se os valores forem diferentes, armazenamos o novo horário para a próxima verificação e retornamos o flag true, indicando a abertura de uma nova barra. Em caso de erro ou se os valores comparados forem iguais, retornamos false, indicando que não houve abertura de uma nova barra.

Para obter os dados do Parabolic SAR e enviá-los à função de trailing, escrevemos uma função de obtenção de dados com base no handle do indicador:

//+------------------------------------------------------------------+
//| Return Parabolic SAR data from the specified timeseries index    |
//+------------------------------------------------------------------+
double GetSARData(const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(ExtHandleSAR, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

Aqui, o processo é semelhante ao da função de obtenção do horário de abertura da barra. Obtemos o valor em um array de dimensão 1 com base no índice passado para a função e, em caso de sucesso, retornamos o valor do array. Em caso de erro, retornamos o valor "vazio" EMPTY_VALUE.

Para definir o StopLoss da posição, é necessário garantir que a distância entre o stop e o preço não esteja dentro do limite estabelecido pelo StopLeveldo símbolo. Se o preço do StopLoss estiver mais próximo do preço do que o permitido pela distância do StopLevel, não será possível definir o stop da posição, e será gerado um erro de "stops incorretos". Para evitar esses erros, precisamos verificar essa distância antes de definir o stop da posição. Existe ainda outro nível — o FreezeLevel— que indica a distância entre o preço e o stop da posição (StopLoss ou TakeProfit), dentro da qual não é possível alterar os níveis de stop, pois a execução desses stops é iminente. No entanto, na maioria dos casos, esses níveis já não são utilizados, e por isso não vamos fazer essa verificação aqui.

Quanto aos níveis StopLevel, há um detalhe: se o nível for definido como 0, isso não significa que ele não exista. Isso indica que os valores são variáveis e geralmente equivalem a duas vezes o spread, ou às vezes três. Esses valores variam de acordo com as configurações do servidor. Para tratar disso, na função que obtém o valor do StopLevel, faremos um parâmetro ajustável: A função receberá um multiplicador pelo qual o spread será multiplicado para obter o nível StopLevel, no caso de o valor do StopLevel ser 0. Se o valor do StopLevel for diferente de zero, esse valor será retornado diretamente.

//+------------------------------------------------------------------+
//| Return StopLevel value of the current symbol in points           |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }


Vamos escrever a função principal do trailing stop:

//+------------------------------------------------------------------+
//| Trailing stop function by StopLoss price value                   |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- 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);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(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);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }

A lógica é simples: no loop sobre a lista de posições abertas no terminal, selecionamos cada posição por seu ticket, verificamos a correspondência do símbolo e do magic number da posição com o filtro configurado para seleção das posições, e verificamos as condições para ajustar o nível de StopLoss. Se as condições forem atendidas, modificamos o stop.

Função para verificar os critérios de modificação dos stops:

//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol 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) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // 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) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // 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;
     }
//--- no matching criteria
   return false;
  }

As condições são simples:

  1. se o StopLoss da posição e o nível para o qual o stop deve ser movido forem iguais, a modificação não é necessária — retornamos false,
  2. se o nível do stop estiver mais próximo do preço do que é permitido pelo StopLevel — não podemos modificar, pois resultaria em erro — retornamos false,
  3. se o preço ainda não percorreu distância suficiente desde a última modificação — é cedo para modificar, pois o passo do trailing não foi atingido — retornamos false,
  4. se o preço não atingiu o lucro definido em pontos, é cedo para modificar — retornamos false.

Essas regras simples são a base do funcionamento de qualquer trailing stop. Se os critérios forem atendidos, o stop deve ser modificado. Se não, as condições são verificadas na próxima chamada da função, e assim por diante.

Vamos escrever a função para modificar o preço do StopLoss da posição com base no ticket:

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- 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,(int)SymbolInfoInteger(Symbol(),SYMBOL_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;
  }

Selecionamos a posição pelo ticket passado para a função, preenchemos os campos necessários da estrutura de solicitação e enviamos a ordem de negociação para o servidor. Em caso de erro, exibimos uma mensagem no log com o código do erro e retornamos false. Mais detalhes sobre operações de negociação podem ser encontrados na documentação da linguagem MQL5.

Finalmente, vamos escrever a função de trailing stop da posição com base nos valores do indicador Parabolic SAR:

//+------------------------------------------------------------------+
//| StopLoss trailing function using the Parabolic SAR indicator     |
//+------------------------------------------------------------------+
void TrailingStopBySAR(const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- get the Parabolic SAR value from the first bar of the timeseries
   double sar=GetSARData(SAR_DATA_INDEX);
   
//--- if failed to obtain data, leave
   if(sar==EMPTY_VALUE)
      return;
      
//--- call the trailing function with the StopLoss price obtained from Parabolic SAR 
   TrailingStopByValue(sar, magic, trailing_step_pt, trailing_start_pt);
  }

Primeiro, obtemos o valor do indicador da barra 1, e se o valor não for obtido, saímos. Se o valor do Parabolic SAR for obtido com sucesso, enviamos para a função de trailing stop com base no valor.

Agora, vamos adicionar o trailing criado com base no Parabolic SAR nos handlers do EA.

No OnTick():

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR();
  }

Em cada abertura de nova barra, a função de trailing pelo Parabolic SAR será chamada com valores padrão — todas as posições abertas no símbolo serão tratadas, independentemente do magic number. O Stop das posições será ajustado desde a sua abertura exatamente conforme os valores do indicador, sem qualquer passo do trailing e sem levar em consideração o lucro das posições em pontos.

No OnTradeTransaction():

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR();
  }

Para que o trailing funcione na abertura de uma posição, no handler será chamada a função de trailing, desde que uma nova transação tenha sido adicionada à lista de negociações do terminal. Sem este handler, o trailing só será acionado na abertura de uma nova barra.

Criamos a função básica de trailing stop e, com base nela, fizemos um EA de trailing pelo indicador Parabolic SAR. Agora é possível compilar o EA e, executando-o no gráfico, abrir uma posição e monitorar o funcionamento do trailing com base nos valores do Parabolic SAR. O arquivo do EA está anexado ao final do artigo para estudo independente.


Usando CTrade da Biblioteca Padrão

A Biblioteca Padrão MQL5 foi escrita em MQL5 e é projetada para facilitar a escrita de programas para usuários finais. A biblioteca oferece acesso conveniente à maioria das funções internas do MQL5.

Vamos aproveitar essa oportunidade e substituir a função de modificação do StopLoss da posição pelo método PositionModify() da classe CTrade.

Vamos analisar o conteúdo do método de modificação da posição:

//+------------------------------------------------------------------+
//| Modify specified opened position                                 |
//+------------------------------------------------------------------+
bool CTrade::PositionModify(const ulong ticket,const double sl,const double tp)
  {
//--- check stopped
   if(IsStopped(__FUNCTION__))
      return(false);
//--- check position existence
   if(!PositionSelectByTicket(ticket))
      return(false);
//--- clean
   ClearStructures();
//--- setting request
   m_request.action  =TRADE_ACTION_SLTP;
   m_request.position=ticket;
   m_request.symbol  =PositionGetString(POSITION_SYMBOL);
   m_request.magic   =m_magic;
   m_request.sl      =sl;
   m_request.tp      =tp;
//--- action and return the result
   return(OrderSend(m_request,m_result));
  }

e compará-lo com a função de modificação dos stops da posição, escrita no EA anteriormente:

//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- 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,(int)SymbolInfoInteger(Symbol(),SYMBOL_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;
  }

Não há grandes diferenças. No método da classe de negociação, logo no início, há uma verificação do sinal de remoção do EA do gráfico. Na função — não.
Além disso, não é usada a função padrão IsStopped(), mas um método de mesmo nome da classe de negociação, com um parâmetro formal:

//+------------------------------------------------------------------+
//| Checks forced shutdown of MQL5-program                           |
//+------------------------------------------------------------------+
bool CTrade::IsStopped(const string function)
  {
   if(!::IsStopped())
      return(false);
//--- MQL5 program is stopped
   PrintFormat("%s: MQL5 program is stopped. Trading is disabled",function);
   m_result.retcode=TRADE_RETCODE_CLIENT_DISABLES_AT;
   return(true);
  }

Aqui, se a função IsStopped() retornar true, primeiro é exibida uma mensagem no log indicando que o EA está sendo removido do gráfico e, em seguida, o sinal de parada do EA é retornado.

As outras diferenças entre a função de negociação e o método da classe são mínimas — é basicamente a mesma implementação feita de formas diferentes. No método da classe, as estruturas são limpas, conforme declaradas no cabeçalho da classe; na função, essas estruturas são locais e declaradas na chamada da função, já inicializando todos os campos com zeros. Na função, se houver um erro no envio da solicitação de negociação, a mensagem com o código de erro é exibida imediatamente, e só depois o resultado é retornado. Já no método da classe de negociação, o resultado da chamada da função OrderSend() é simplesmente retornado.

Faremos modificações no EA já escrito, salvando-o com um novo nome TrailingBySAR_02.mq5. No EA, adicionaremos a capacidade de testar o trailing no Strategy Tester, abrindo posições com base nos valores do indicador Parabolic SAR e ajustando os níveis de StopLoss das posições abertas segundo os valores desse mesmo indicador.

Vincularemos ao EA o arquivo da classe de negociação, declararemos o valor do magic number do EA nas variáveis de entrada, na área global declararemos uma instância da classe de negociação, e no handler OnInit() atribuiremos ao objeto da classe de negociação o valor do magic number das variáveis de entrada:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_02.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"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include <Trade\Trade.mqh>    // replace trading functions with Standard Library methods

//--- input 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 ulong             InpMagic          =  123;              // Magic Number

//--- global variables
int      ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle
double   ExtStepSAR=0;                 // Parabolic SAR step
double   ExtMaximumSAR=0;              // Parabolic SAR maximum
CTrade   ExtTrade;                     // trading operations class instance

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- set the magic number to the trading class object
   ExtTrade.SetExpertMagicNumber(InpMagic);

//--- set the Parabolic SAR parameters within acceptable limits
   ExtStepSAR   =(InpStepSAR<0.0001 ? 0.0001 : InpStepSAR);
   ExtMaximumSAR=(InpMaximumSAR<0.0001 ? 0.0001 : InpMaximumSAR);
   
//--- if there is an error creating the indicator, display a message in the journal and exit from OnInit with an error
   ExtHandleSAR =iSAR(Symbol(), InpTimeframeSAR, ExtStepSAR, ExtMaximumSAR);
   if(ExtHandleSAR==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  Symbol(), TimeframeDescription(InpTimeframeSAR), ExtStepSAR, ExtMaximumSAR, GetLastError());
      return(INIT_FAILED);
     }   
//--- successful
   return(INIT_SUCCEEDED);
  }


No handler OnTick() do EA adicionaremos o código para abrir posições com base no Parabolic SAR no Strategy Tester, e na função de trailing adicionaremos a passagem do magic number do EA:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
   if(MQLInfoInteger(MQL_TESTER))
     {
      //--- get Parabolic SAR data from bars 1 and 2
      double sar1=GetSARData(SAR_DATA_INDEX);
      double sar2=GetSARData(SAR_DATA_INDEX+1);
      
      //--- if the price structure is filled out and Parabolic SAR data is obtained
      MqlTick tick={};
      if(SymbolInfoTick(Symbol(), tick) && sar1!=EMPTY_VALUE && sar2!=EMPTY_VALUE)
        {
         //--- if Parabolic SAR on bar 1 is below Bid price, while on bar 2 it is above Bid price, open a long position
         if(sar1<tick.bid && sar2>tick.bid)
            ExtTrade.Buy(0.1);
         //--- if Parabolic SAR on bar 1 is above Ask, while on bar 2 it is below Ask price, open a long position
         if(sar1>tick.ask && sar2<tick.ask)
            ExtTrade.Sell(0.1);
        }
     }
      
//--- trail position stops by Parabolic SAR
   TrailingStopBySAR(InpMagic);
  }


No handler OnTradeTransaction(), também adicionaremos a passagem do magic number para a função de trailing:

//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingStopBySAR(InpMagic);
  }

O uso do magic number no EA para abrir posições no testador e trailing de seus stops permitirá verificar o funcionamento do filtro pelo magic number.

Na função de trailing universal, a chamada da função de modificação será substituídapela chamada do método da classe de negociação:

//+------------------------------------------------------------------+
//| Universal trailing stop function by StopLoss price value         |
//+------------------------------------------------------------------+
void TrailingStopByValue(const double value_sl, const long magic=-1, const int trailing_step_pt=0, const int trailing_start_pt=0)
  {
//--- price structure
   MqlTick tick={};
//--- 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);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(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);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ExtTrade.PositionModify(pos_ticket, value_sl, PositionGetDouble(POSITION_TP));
     }
  }

Agora, para modificar o stop da posição, não chamaremos mais a função escrita anteriormente, mas sim o método PositionModify() da classe CTrade. A diferença entre essas chamadas está em um parâmetro. Se a função foi escrita para modificar apenas o preço do StopLoss da posição, ao definir os parâmetros, era necessário passar o valor do ticket da posição a ser modificada e o novo nível de stop. Agora, no método da classe de negociação, é preciso passar três parâmetros — além do ticket da posição e do valor do nível de StopLoss, também é necessário especificar o nível de TakeProfit. Como a posição já foi selecionada e o valor de seu TakeProfit não precisa ser alterado, o valor de TakeProfit é passado sem modificações diretamente das propriedades da posição selecionada.

A função ModifySL() anteriormente escrita foi removida do código do EA.

Agora, vamos compilar o EA e executá-lo no Strategy Tester em qualquer símbolo e timeframe, utilizando o modelo "Todos os tiks":


Como podemos ver, os stops das posições são corretamente ajustados de acordo com o valor da primeira barra do indicador Parabolic SAR.

O arquivo do EA pode ser consultado no final do artigo para estudo independente.


Trailing Stop pronto no EA "em algumas linhas"

Apesar da facilidade de criação do trailing e de sua utilização no EA, seria conveniente não ter que escrever todas as funções necessárias para o seu funcionamento em cada novo EA. Seria melhor se tudo funcionasse no estilo "Plug & Play".
E isso é possível no MQL5. Para isso, basta escrever o arquivo de inclusão uma única vez e, em seguida, apenas incorporá-lo ao EA necessário e chamar as funções de trailing onde for necessário.

Vamos transferir todas as funções de trailing criadas para um novo arquivo. Para isso, criaremos um novo arquivo de inclusão na pasta do EA, com o nome TrailingsFunc.mqh. De modo geral, é recomendável armazenar todos esses arquivos em uma pasta comum para arquivos de inclusão \MQL5\Include\, ou em uma subpasta dentro deste diretório. No entanto, para este teste, basta criar o arquivo diretamente na pasta do EA e incorporá-lo a partir de lá.

No editor, pressione Ctrl+N e selecione um novo arquivo de inclusão:


Na próxima janela do assistente, insira o nome do arquivo de inclusão TrailingFunc.
É importante notar que, por padrão, o caminho raiz para os arquivos de inclusão já está preenchido — Include. Ou seja, o arquivo será criado nesta pasta.
Em seguida, você pode movê-lo para a pasta do EA ou definir o caminho correto na linha de nome do arquivo (substituindo Include\ por Experts\ e, em seguida, o caminho para a pasta com os EAs de teste, se houver, e depois o nome do arquivo TrailingFunc):

Clique em "Concluir" para criar o arquivo vazio:

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+


Agora, todas as funções previamente criadas nos EAs de teste devem ser transferidas para este arquivo:

//+------------------------------------------------------------------+
//|                                                TrailingsFunc.mqh |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Simple trailing by value                                         |
//+------------------------------------------------------------------+
void SimpleTrailingByValue(const double value_sl, const long magic=-1, 
                           const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- price structure
   MqlTick tick={};
   
//--- 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);
      
      //--- skip positions that do not match the filter by symbol and magic number
      if((magic!=-1 && pos_magic!=magic) || pos_symbol!=Symbol())
         continue;
         
      //--- if failed to get the prices, move on
      if(!SymbolInfoTick(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);
      
      //--- if StopLoss modification conditions are suitable, modify the position stop level
      if(CheckCriterion(pos_type, pos_open, pos_sl, value_sl, trailing_step_pt, trailing_start_pt, tick))
         ModifySL(pos_ticket, value_sl);
     }
  }
//+------------------------------------------------------------------+
//|Check the StopLoss modification criteria and return a flag        |
//+------------------------------------------------------------------+
bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, 
                    int trailing_step_pt, int trailing_start_pt, MqlTick &tick)
  {
//--- if the stop position and the stop level for modification are equal, return 'false'
   if(NormalizeDouble(pos_sl-value_sl, Digits())==0)
      return false;

   double trailing_step = trailing_step_pt * Point(); // convert the trailing step into price
   double stop_level    = StopLevel(2) * Point();     // convert the StopLevel of the symbol 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) / Point());             // calculate the position profit in points
        if(tick.bid - stop_level > value_sl                             // if the price and the StopLevel level pending from it are higher than the StopLoss level (the distance to StopLevel is observed) 
           && pos_sl + trailing_step < value_sl                         // if the StopLoss level exceeds the trailing step based on the current StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // 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) / Point());             // position profit in points
        if(tick.ask + stop_level < value_sl                             // if the price and the StopLevel level pending from it are lower than the StopLoss level (the distance to StopLevel is observed)
           && (pos_sl - trailing_step > value_sl || pos_sl==0)          // if the StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss
           && (trailing_start_pt==0 || pos_profit_pt>trailing_start_pt) // 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;
     }
//--- no matching criteria
   return false;
  }
//+------------------------------------------------------------------+
//| Modify StopLoss of a position by ticket                          |
//+------------------------------------------------------------------+
bool ModifySL(const ulong ticket, const double stop_loss)
  {
//--- 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,(int)SymbolInfoInteger(request.symbol,SYMBOL_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;
  }
//+------------------------------------------------------------------+
//| Return StopLevel in points                                       |
//+------------------------------------------------------------------+
int StopLevel(const int spread_multiplier)
  {
   int spread    =(int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD);
   int stop_level=(int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
   return(stop_level==0 ? spread * spread_multiplier : stop_level);
  }
//+------------------------------------------------------------------+
//| Return timeframe description                                     |
//+------------------------------------------------------------------+
string TimeframeDescription(const ENUM_TIMEFRAMES timeframe)
  {
   return(StringSubstr(EnumToString(timeframe==PERIOD_CURRENT ? Period() : timeframe), 7));
  }
//+------------------------------------------------------------------+
//| Return new bar opening flag                                      |
//+------------------------------------------------------------------+
bool IsNewBar(void)
  {
   static datetime time_prev=0;
   datetime        bar_open_time=TimeOpenBar(0);
   if(bar_open_time==0)
      return false;
   if(bar_open_time!=time_prev)
     {
      time_prev=bar_open_time;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| Return the bar opening time by timeseries index                  |
//+------------------------------------------------------------------+
datetime TimeOpenBar(const int index)
  {
   datetime array[1];
   ResetLastError();
   if(CopyTime(NULL, PERIOD_CURRENT, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyTime() failed. Error %d", __FUNCTION__, GetLastError());
      return 0;
     }
   return array[0];
  }

No EA, havia uma função para obter dados do indicador Parabolic SAR. Os dados nessa função eram obtidos por meio do handle do indicador fornecido. Isso significa que o indicador usado pode não ser apenas o Parabolic SAR, mas qualquer outro adequado para definir os níveis de StopLoss das posições.

Portanto, essa função foi renomeada para uma função geral de obtenção de dados de indicadores por handle:

//+------------------------------------------------------------------+
//| Return indicator data by handle                                  |
//| from the specified timeseries index                              |
//+------------------------------------------------------------------+
double GetIndData(const int handle_ind, const int index)
  {
   double array[1];
   ResetLastError();
   if(CopyBuffer(handle_ind, 0, index, 1, array)!=1)
     {
      PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

Consequentemente, agora existe uma função de trailing baseada nos dados do indicador, obtidos pela função descrita acima:

//+------------------------------------------------------------------+
//| Trailing by indicator data specified by handle                   |
//+------------------------------------------------------------------+
void TrailingByDataInd(const int handle_ind, const int index=1, const long magic=-1, 
                       const int trailing_step_pt=0, const int trailing_start_pt=0, const int trailing_offset_pt=0)
  {
//--- get the Parabolic SAR value from the specified timeseries index
   double data=GetIndData(handle_ind, index);
   
//--- if failed to obtain data, leave
   if(data==EMPTY_VALUE)
      return;
      
//--- call the simple trailing function with the StopLoss price obtained from Parabolic SAR 
   SimpleTrailingByValue(data, magic, trailing_step_pt, trailing_start_pt, trailing_offset_pt);
  }

Na função, primeiro obtemos os dados do indicador com base no handle e no índice da barra especificado, e em seguida chamamos a função de trailing, passando para ela o valor obtido do indicador para ajustar o StopLoss.

Dessa forma, podemos ajustar os stops das posições usando os dados de qualquer indicador apropriado para essa finalidade.

Para evitar inserir no código do EA a criação do indicador Parabolic SAR, incluiremos tal função no arquivo:

//+------------------------------------------------------------------+
//| Create and return the Parabolic SAR handle                       |
//+------------------------------------------------------------------+
int CreateSAR(const string symbol_name, const ENUM_TIMEFRAMES timeframe, const double step_sar=0.02, const double max_sar=0.2)
  {
//--- set the indicator parameters within acceptable limits
   double step=(step_sar<0.0001 ? 0.0001 : step_sar);
   double max =(max_sar <0.0001 ? 0.0001 : max_sar);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ResetLastError();
   int handle=iSAR(symbol, period, step, max);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d",
                  symbol, TimeframeDescription(period), step, max, GetLastError());
     } 
//--- return the result of creating the indicator handle
   return handle;
  }

Na verdade, a função CreateSAR() é o código transferido do handler OnInit() do EA de teste TrailingBySAR_01.mq5. Esse método permitirá chamar a função diretamente, sem a necessidade de adicionar no EA as linhas de correção das variáveis de entrada do indicador e a criação de seu handle.

Mais adiante no código, encontramos funções semelhantes para a criação de várias médias móveis, por exemplo, a função para criar a Média Móvel Adaptativa:

//+------------------------------------------------------------------+
//| Create and return Adaptive Moving Average handle                 |
//+------------------------------------------------------------------+
int CreateAMA(const string symbol_name, const ENUM_TIMEFRAMES timeframe,
              const int ama_period=9, const int fast_ema_period=2, const int slow_ema_period=30, const int shift=0, const ENUM_APPLIED_PRICE price=PRICE_CLOSE)
  {
//--- set the indicator parameters within acceptable limits
   int ma_period=(ama_period<1 ? 9 : ama_period);
   int fast_ema=(fast_ema_period<1 ? 2 : fast_ema_period);
   int slow_ema=(slow_ema_period<1 ? 30 : slow_ema_period);

//--- adjust the symbol and timeframe values
   ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? Period() : timeframe);
   string          symbol=(symbol_name==NULL || symbol_name=="" ? Symbol() : symbol_name);
 
//--- create indicator handle
   ::ResetLastError();
   int handle=::iAMA(symbol, period, ma_period, fast_ema, slow_ema, shift, price);
   
//--- if there is an error creating the indicator, display an error message in the journal
   if(handle==INVALID_HANDLE)
     {
      ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d",
                    symbol, TimeframeDescription(period), ma_period, fast_ema, slow_ema,
                    ::StringSubstr(::EnumToString(price),6), ::GetLastError());
     }
//--- return the result of creating the indicator handle
   return handle;
  }

Todas as outras funções são semelhantes à mostrada acima, e não há necessidade de analisá-las aqui — elas podem ser conferidas no arquivo anexado ao artigo, TrailingsFunc.mqh.

Essas funções foram criadas para facilitar a criação rápida de médias móveis, de modo que seus dados possam ser usados em vez dos dados do Parabolic SAR durante a realização de pesquisas para desenvolver diferentes tipos de trailing stop.


Para testar as funções escritas, criaremos um novo EA de teste com o nome TrailingBySAR_03.mq5 e anexaremos o arquivo incluído recém-criado TrailingsFunc.mqh.
Na área global, declararemos uma variável para armazenar o handle do indicador criado, e no handler OnInit() atribuiremos a essa variável o resultado da criação do indicador ParabolicSAR:

//+------------------------------------------------------------------+
//|                                             TrailingBySAR_03.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"

#define   SAR_DATA_INDEX   1  // bar we get Parabolic SAR data from

#include "TrailingsFunc.mqh"

//--- input 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 long              InpMagic          =  123;              // Expert Magic Number

//--- global variables
int   ExtHandleSAR=INVALID_HANDLE;  // Parabolic SAR handle

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create Parabolic SAR handle
   ExtHandleSAR=CreateSAR(Symbol(), InpTimeframeSAR, InpStepSAR, InpMaximumSAR);
   
//--- if there is an error creating the indicator, exit OnInit with an error
   if(ExtHandleSAR==INVALID_HANDLE)
      return(INIT_FAILED);

//--- successful
   return(INIT_SUCCEEDED);
  }


Agora resta apenas iniciar o trailing com os dados do indicador criado nos handlers OnTick() e OnTradeTransaction(), passando para a função de trailing o magic das posições, especificado nas configurações do EA:

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- if not a new bar, leave the handler
   if(!IsNewBar())
      return;
      
//--- trail position stops by Parabolic SAR
   TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }
//+------------------------------------------------------------------+
//| TradeTransaction function                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction& trans,
                        const MqlTradeRequest& request,
                        const MqlTradeResult& result)
  {
   if(trans.type==TRADE_TRANSACTION_DEAL_ADD)
      TrailingByDataInd(ExtHandleSAR, SAR_DATA_INDEX, InpMagic);
  }

O EA criado realiza trailing-stop com base nos dados do indicador Parabolic SAR. Ele fará o trailing dos stops das posições abertas no símbolo onde o EA está rodando, desde que o magic coincida com o configurado nas definições do EA.


Incorporando o trailing stop ao EA

Por fim, vamos anexar o trailing do Parabolic SAR ao EA ExpertMACD padrão, localizado em \MQL5\Experts\Advisors\ExpertMACD.mq5.

Vamos salvar com um novo nome ExpertMACDPSAR.mq5 e fazer as modificações necessárias para ativar o trailing.

Na área global, incluímos o arquivo das funções de trailing, adicionamos os parâmetros de entrada do trailing e declaramos uma variável para armazenar o handle do Parabolic SAR criado:

//+------------------------------------------------------------------+
//|                                                   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 "TrailingsFunc.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;

//--- inputs for trail
input group  " - PSAR Trailing Parameters -"
input bool   InpUseTrail       =  true;      // Trailing is Enabled
input double InpSARStep        =  0.02;      // Trailing SAR Step
input double InpSARMaximum     =  0.2;       // Trailing SAR Maximum
input int    InpTrailingStart  =  0;         // Trailing start
input int    InpTrailingStep   =  0;         // Trailing step in points
input int    InpTrailingOffset =  0;         // Trailing offset in points
//+------------------------------------------------------------------+
//| Global expert object                                             |
//+------------------------------------------------------------------+
CExpert ExtExpert;
int     ExtHandleSAR;
//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+


No handler OnInit()criamos o indicador e gravamos seu handle na variável:

//+------------------------------------------------------------------+
//| Initialization function of the expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Initializing trail
   if(InpUseTrail)
     {
      ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT,InpSARStep,InpSARMaximum);
      if(ExtHandleSAR==INVALID_HANDLE)
         return(INIT_FAILED);
     }
   
//--- Initializing expert
//...
//...


No handler OnDeinit() removeremos o handle e liberaremos a parte de cálculo do indicador:

//+------------------------------------------------------------------+
//| Deinitialization function of the expert advisor                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ExtExpert.Deinit();
   IndicatorRelease(ExtHandleSAR);
  }


Nos handlers OnTick() e OnTrade() iniciaremos o trailing com base no indicador Parabolic SAR:

//+------------------------------------------------------------------+
//| Function-event handler "tick"                                    |
//+------------------------------------------------------------------+
void OnTick(void)
  {
   ExtExpert.OnTick();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }
//+------------------------------------------------------------------+
//| Function-event handler "trade"                                   |
//+------------------------------------------------------------------+
void OnTrade(void)
  {
   ExtExpert.OnTrade();
   TrailingByDataInd(ExtHandleSAR, 1, Expert_MagicNumber, InpTrailingStep, InpTrailingStart, InpTrailingOffset);
  }

Isso é tudo o que precisamos adicionar ao arquivo do EA para implementar o trailing completo com o Parabolic SAR.

No mesmo EA, também é possível criar o handle de outro indicador, como a média móvel, e passar seu handle para a função de trailing. Nesse caso, o trailing será baseado na média móvel. É possível combinar os valores obtidos de diferentes indicadores e passá-los como valores calculados agregados para a função de trailing, ou ainda, em diferentes situações de mercado, ajustar os stops com diferentes algoritmos, passando o valor calculado necessário para o trailing das posições. As possibilidades de experimentação são amplas.

É importante lembrar: as funções apresentadas no arquivo TrailingFunc.mqh permitem criar

  1. EAs de trailing que não realizam operações de negociação, utilizando diferentes algoritmos ou dados de indicadores,
  2. anexar diferentes trailing stops a EAs de negociação existentes, baseados em dados de indicadores ou em algoritmos próprios.

Mas há limitações: não podemos passar diferentes valores simultaneamente para o trailing de posições longas e curtas, e não podemos fazer o trailing de posições abertas em outro símbolo. Também existem alguns inconvenientes ao incorporar o trailing — é necessário criar os indicadores no EA e armazenar seus handles em variáveis. De tudo isso, e um pouco mais, poderemos nos livrar criando classes de trailing. Mas esse será o tema do próximo artigo, onde essas possibilidades serão exploradas.

Vamos executar o EA criado em um teste simples com os parâmetros definidos.

O intervalo e as configurações de teste serão as seguintes:

  • Símbolo: EURUSD,
  • Timeframe: M15,
  • Teste no último ano com todos os tiks, sem atraso na execução.

Configurações dos parâmetros de entrada:


Após o teste no intervalo definido, com o trailing desligado, obtemos as seguintes estatísticas:



Agora vamos rodar o EA, ativando o trailing nas configurações:


É visível que após a ativação do trailing, o gráfico ficou um pouco mais estável. Convido os leitores a experimentar diferentes soluções para testar os trailings.

Todos os arquivos estão anexados ao artigo para estudo e testes independentes.


Considerações finais

Assim, aprendemos a criar e anexar trailing-stop rapidamente a um EA. Tudo o que é necessário para adicionar o trailing-stop a qualquer EA é:

  1. colocar o arquivo incluído TrailingsFunc.mqh na pasta do EA,
  2. anexar este arquivo ao EA com o comando #include "TrailingsFunc.mqh",
  3. incluir a criação do indicador ParabolicSAR no OnInit() do EA: ExtHandleSAR=CreateSAR(NULL,PERIOD_CURRENT);
  4. incluir nos handlers OnTick() e (se necessário) em OnTrade() ou OnTradeTransaction() o chamado do trailing: TrailingByDataInd(ExtHandleSAR);
  5. no OnDeinit() do EA, liberar a parte de cálculo do ParabolicSAR com IndicatorRelease(ExtHandleSAR).


Agora este EA terá um trailing-stop completo para gerenciar suas posições. Podemos fazer trailing não apenas com o indicador Parabolic SAR, mas com qualquer outro indicador. Também podemos criar nossos próprios algoritmos para calcular os níveis de stop-loss.

É possível usar múltiplos trailings com diferentes parâmetros em um único EA e alternar entre eles dependendo da situação do mercado.  No próximo artigo, discutiremos classes de trailing, que não possuem as limitações das funções simples.



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

Arquivos anexados |
ExpertMACDPSAR.mq5 (14.04 KB)
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.
Redes neurais de maneira fácil (Parte 87): Segmentação de séries temporais Redes neurais de maneira fácil (Parte 87): Segmentação de séries temporais
A previsão desempenha um papel importante na análise de séries temporais. No novo artigo, falaremos sobre as vantagens da segmentação de séries temporais.
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.
Do básico ao intermediário: Comando SWITCH Do básico ao intermediário: Comando SWITCH
Neste artigo iremos aprender como utilizar o comando SWITCH em sua forma mais simples e básica. O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como sendo, uma aplicação cuja finalidade não venha a ser o aprendizado e estudo dos conceitos mostrados.