Robôs & MetaTrader

Trailing stop em MQL5: como programar no seu robô (com código comentado)

Código completo de trailing stop em MQL5 para MetaTrader 5, com início, distância e passo configuráveis, ajuste ao tick da B3 e os erros mais comuns que impedem o stop de ser movido.

Neste artigo

O trailing stop é um stop que “anda” junto com o preço quando a operação vai a seu favor. Ele protege o lucro já conquistado sem limitar o ganho caso o movimento continue.

O MetaTrader 5 até tem um trailing stop nativo (clique direito na posição → Trailing Stop), mas ele é limitado: usa distâncias fixas, só funciona com o terminal aberto e não conversa com a lógica do seu robô. Quando o trailing é programado dentro do Expert Advisor, você controla exatamente quando ele começa, a que distância fica e de quanto em quanto ele se move.

Neste artigo, você vai ver um código completo em MQL5, entender cada parte e conhecer os erros que mais aparecem na prática.

Os três parâmetros: início, distância e passo

Um trailing stop bem feito tem três configurações:

  • Início: quanto de lucro, em pontos, a operação precisa ter para o trailing começar a agir.
  • Distância: a quantos pontos do preço atual o stop vai ficar.
  • Passo: o movimento mínimo para o stop ser ajustado. Evita mandar uma modificação a cada tick.

Veja um exemplo no mini índice (WIN), comprado em 130.000, com início de 200 pontos, distância de 150 e passo de 50:

Preço atualO que aconteceStop
130.150Lucro de 150 pontos, ainda abaixo do iníciostop original
130.200Trailing ativado130.050
130.230Novo stop seria 130.080, só 30 pontos acima: menor que o passo130.050
130.260Novo stop seria 130.110, 60 pontos acima: move130.110
O stop só sobe em degraus: quando o preço avança pelo menos o passo configurado.

O código completo

O código abaixo pode ser colado em um Expert Advisor existente. Ele filtra as posições pelo ativo e pelo magic number, calcula o novo stop e só modifica quando necessário.

#include <Trade\Trade.mqh>

input ulong InpMagic          = 20260928; // Magic number das posições do robô
input int   InpTrailingInicio = 200;      // Lucro (em pontos) para ativar o trailing
input int   InpTrailingDist   = 150;      // Distância do stop até o preço (em pontos)
input int   InpTrailingPasso  = 50;       // Movimento mínimo (em pontos) para ajustar

CTrade trade;

int OnInit()
{
   trade.SetExpertMagicNumber(InpMagic);
   return(INIT_SUCCEEDED);
}

void OnTick()
{
   // ... sua lógica de entrada aqui ...
   AplicarTrailingStop();
}

//--- Arredonda o preço para um múltiplo do tick (essencial na B3)
double AjustarAoTick(double preco)
{
   double tick = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(tick <= 0)
      return NormalizeDouble(preco, _Digits);
   return NormalizeDouble(MathRound(preco / tick) * tick, _Digits);
}

void AplicarTrailingStop()
{
   double nivelMin = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);   // também seleciona a posição
      if(ticket == 0)
         continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol)
         continue;
      if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
         continue;

      double abertura = PositionGetDouble(POSITION_PRICE_OPEN);
      double slAtual  = PositionGetDouble(POSITION_SL);
      double tpAtual  = PositionGetDouble(POSITION_TP);

      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
      {
         double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
         if(bid <= 0 || bid - abertura < InpTrailingInicio * _Point)
            continue;                              // ainda sem o lucro mínimo

         double novoSL = AjustarAoTick(bid - InpTrailingDist * _Point);
         if(bid - novoSL < nivelMin)
            continue;                              // perto demais: seria recusado

         if(slAtual == 0 || novoSL - slAtual >= InpTrailingPasso * _Point)
         {
            if(!trade.PositionModify(ticket, novoSL, tpAtual))
               Print("Falha ao mover o stop: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription());
         }
      }
      else // POSITION_TYPE_SELL
      {
         double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
         if(ask <= 0 || abertura - ask < InpTrailingInicio * _Point)
            continue;

         double novoSL = AjustarAoTick(ask + InpTrailingDist * _Point);
         if(novoSL - ask < nivelMin)
            continue;

         if(slAtual == 0 || slAtual - novoSL >= InpTrailingPasso * _Point)
         {
            if(!trade.PositionModify(ticket, novoSL, tpAtual))
               Print("Falha ao mover o stop: ", trade.ResultRetcode(), " ", trade.ResultRetcodeDescription());
         }
      }
   }
}

Entendendo as partes importantes

O caminho que o código percorre a cada novo preço recebido.

O laço de trás para frente

O for percorre as posições do fim para o começo. É um hábito seguro em MQL5: se alguma posição for fechada durante o laço, a contagem não pula nenhuma outra.

O filtro por magic number

Sem esse filtro, o robô vai mexer no stop de qualquer posição do ativo, inclusive operações manuais ou de outros robôs. É um dos erros mais comuns em códigos copiados da internet.

Compra usa Bid, venda usa Ask

Uma posição comprada é encerrada pelo preço de venda (Bid). Uma vendida é encerrada pelo preço de compra (Ask). Por isso o cálculo usa preços diferentes para cada lado. Usar o preço errado faz o stop ficar alguns pontos fora do lugar, o que pesa em ativos com spread maior.

Ajuste ao tick

Na B3, o preço precisa respeitar o tamanho mínimo de variação do ativo. No WIN, esse tamanho é de 5 pontos: um stop em 130.112 é inválido, e a corretora recusa a ordem. A função AjustarAoTick arredonda o preço para o múltiplo válido mais próximo. No Forex, o tick costuma ser igual ao ponto, então a função não atrapalha.

Nível mínimo de stop

Algumas corretoras exigem uma distância mínima entre o preço atual e o stop (SYMBOL_TRADE_STOPS_LEVEL). Na B3 esse valor geralmente é zero, mas em contas de Forex é comum ter um mínimo. Se o novo stop ficar perto demais, a modificação seria recusada, então o código nem tenta.

O passo

Sem o passo, o robô enviaria uma modificação a cada tick de lucro. Isso gera centenas de requisições desnecessárias, polui o diário e, em algumas corretoras, pode até causar bloqueio temporário por excesso de pedidos.

Variações úteis

Trailing pela volatilidade (ATR)

Em vez de uma distância fixa, use um múltiplo do ATR. Assim o stop fica mais largo em dias agitados e mais curto em dias calmos:

int handleATR;

int OnInit()
{
   handleATR = iATR(_Symbol, PERIOD_CURRENT, 14);
   if(handleATR == INVALID_HANDLE)
      return(INIT_FAILED);
   trade.SetExpertMagicNumber(InpMagic);
   return(INIT_SUCCEEDED);
}

double DistanciaATR(double multiplicador)
{
   double atr[];
   if(CopyBuffer(handleATR, 0, 1, 1, atr) != 1)
      return 0;
   return atr[0] * multiplicador;   // já em preço, não em pontos
}

Repare que, em MQL5, iATR devolve um handle, e o valor é lido com CopyBuffer. Quem vem do MQL4 costuma esquecer isso.

Breakeven antes do trailing

Uma combinação popular: ao atingir X pontos de lucro, o stop vai para o preço de entrada (mais uma pequena margem para cobrir custos). Só depois de um lucro maior o trailing assume.

Trailing pelo candle anterior

Na compra, o stop acompanha a mínima do candle anterior (iLow(_Symbol, PERIOD_CURRENT, 1)). Na venda, a máxima (iHigh). É simples e respeita a estrutura do gráfico.

Como testar no Testador de Estratégia

O trailing stop depende do caminho que o preço faz dentro do candle. Por isso:

  • Use a modelagem “Cada tick baseado em ticks reais”. Com “Apenas preços de abertura”, o resultado do trailing não significa nada.
  • Rode pelo menos uma vez no modo visual e acompanhe o stop se movendo no gráfico.
  • Compare o resultado com e sem trailing. Nem toda estratégia melhora com ele: em estratégias de alvo curto, o trailing pode apenas reduzir o ganho médio.

Se nunca fez um backtest, veja o passo a passo de backtest no MetaTrader 5.

Erros comuns e o que significam

Mensagem no diárioCausa provável
10016 Invalid stopsPreço fora do tick do ativo ou stop perto demais do preço
10025 No changesO robô enviou o mesmo stop que já estava na posição
Stop mexendo em operações manuaisFalta o filtro por magic number
Trailing não funciona no backtestModelagem por preços de abertura em vez de ticks

Resumo

  • Trailing stop programado no robô dá controle total sobre início, distância e passo.
  • Filtre sempre pelo ativo e pelo magic number.
  • Na B3, ajuste o preço ao tick do ativo para não ter ordens recusadas.
  • Teste com ticks reais e compare o resultado com e sem trailing.

O código deste artigo é um ponto de partida para estudo. Antes de usar com dinheiro real, teste em conta demo e adapte às regras da sua estratégia.

#MQL5#trailing stop#MetaTrader 5#programação
Escrito por

Jackson Jordão

Programador de robôs de trading (MQL4/MQL5) e de sistemas para empresas, fundador da Digitalmaster12. Escreve sobre o que usa no dia a dia: automação, MetaTrader 5, IA e software sob medida. Saiba mais.

Continue lendo

Artigos relacionados

Ver tudo em Robôs & MetaTrader