Invalid Stops (10016/130) — o SL no MT5/MT4
Sumário
- O que é Invalid Stops (diferença entre 10016 e 130)
- ① TRADE_RETCODE_INVALID_STOPS = 10016 (MT5 / código de retorno do `OrderSend()`)
- ② ERR_INVALID_STOPS = 130 (MT4 / `GetLastError()`)
- Diagnóstico rápido em 30 segundos
- Causas e soluções (6 padrões)
- ① SL/TP muito próximo do preço atual (abaixo do stop level)
- ② Direção do SL/TP invertida (troca entre BUY/SELL)
- ③ Modificação de ordem/posição dentro do freeze level
- ④ Confusão entre preço e pontos (distância)
- ⑤ Corretoras com execução a mercado não aceitam SL/TP na abertura da ordem
- ⑥ Particularidades específicas do ativo (ouro e índices têm stop level maior)
- Diferenças entre corretoras (ponto de atenção)
- Código MQL5 para prevenir esse erro (para desenvolvedores de EA)
- Validar e ajustar (clamp) o SL/TP antes de enviar a ordem
- Tratar o retcode 10016 individualmente
- Só modifique o trailing depois de checar o freeze level
- Checklist de prioridades
- Conclusão
- Perguntas Frequentes
- P: O cálculo do SL/TP deveria estar correto, mas ainda assim aparece Invalid stops. Por quê?
- P: Qual é a diferença entre 10016 e 130?
- P: Normalmente não acontece, mas o Invalid stops aparece só durante a divulgação de indicadores econômicos.
- P: A ordem passa sem SL/TP, mas é rejeitada quando inclui SL/TP.
- P: Um EA que funciona no EURUSD gera Invalid stops repetidamente no ouro.
Invalid Stops (10016/130) Totalmente Resolvido
Quando você está rodando um EA e vê Invalid stops ou OrderSend error 130 na aba Expert, é fácil pensar "o valor do SL está errado? Mas o cálculo deveria estar certo...". Na verdade, a grande maioria desses erros não vem de um erro de cálculo do SL/TP, e sim da violação de uma regra de distância mínima definida pela corretora. Mesmo que o valor em si esteja correto, ele é rejeitado se estiver muito próximo do preço atual, na direção invertida, ou dentro de uma zona onde a modificação está proibida.
Este artigo é voltado tanto para quem usa EAs no MT5/MT4 quanto para quem programa EAs em MQL5, e reúne em um só lugar o que realmente é o Invalid stops, suas 6 causas, um diagnóstico de 30 segundos e a prevenção permanente no código. Para uma lista geral de códigos de erro, consulte o Guia Completo de Códigos de Erro do MQL5/MT5.
Este artigo parte do MT5 (série de builds 4xxx) de julho de 2026. Os valores específicos de stop level variam conforme a corretora e o ativo.
O que é Invalid Stops (diferença entre 10016 e 130)
O valor que indica "stop inválido" existe em duas versões, conforme a plataforma MT5 ou MT4 (geração). Identificar em qual plataforma e em qual etapa o erro apareceu acelera muito a investigação da causa.
① TRADE_RETCODE_INVALID_STOPS = 10016 (MT5 / código de retorno do OrderSend())
No MQL5, o resultado do OrderSend() fica em MqlTradeResult.retcode. Se o SL/TP da requisição (ou a relação com o preço de uma ordem pendente) não obedecer às regras do servidor, ela é rejeitada com 10016 (TRADE_RETCODE_INVALID_STOPS). Essa é uma notificação de rejeição vinda do servidor de negociação.
Significado: Stop (SL/TP) inválido na requisição (Invalid stops in the request)
Constante: TRADE_RETCODE_INVALID_STOPS
Valor : 10016
// Exemplo típico de saída visível no log
2026.07.07 09:15:32.441 EA_NAME XAUUSD,M5: OrderSend error: retcode=10016 (invalid stops)
② ERR_INVALID_STOPS = 130 (MT4 / GetLastError())
Na geração MT4 (MQL4), quando OrderSend() / OrderModify() falha, o GetLastError() retorna 130 (ERR_INVALID_STOPS). É isso que aparece como OrderSend error 130 na aba Expert. Se você também usa um EA na versão MT4, é esse erro que vai surgir.
Significado: Stop inválido (invalid stops)
Constante: ERR_INVALID_STOPS
Valor : 130
Uso prático — como diferenciar:
| Plataforma | Origem | Valor | Quando aparece |
|---|---|---|---|
| MT5 | MqlTradeResult.retcode | 10016 (TRADE_RETCODE_INVALID_STOPS) | OrderSend / PositionModify rejeitado pelo servidor |
| MT5 | CTrade.ResultRetcode() | 10016 | Envio/modificação de ordem via CTrade rejeitado |
| MT4 | GetLastError() | 130 (ERR_INVALID_STOPS) | Após falha de OrderSend / OrderModify |
Os números são diferentes, mas o significado e a causa são praticamente os mesmos, e o tratamento é comum aos dois. Vale notar que o 10015 (TRADE_RETCODE_INVALID_PRICE), frequentemente confundido com o 10016 no MT5, indica que o próprio preço da ordem é inválido — é um problema diferente. O 10016 trata exclusivamente da posição do SL/TP (stop).
Diagnóstico rápido em 30 segundos
Abra "Observação do Mercado → clique direito no símbolo → Especificação" no MT5 e verifique estes dois itens:
Stop level (Stops level) : distância mínima (em pontos) que o SL/TP deve manter do preço atual
Freeze level (Freeze level): distância (em pontos) dentro da qual fica proibido modificar/cancelar uma ordem prestes a ser executada
Em seguida, compare no log, no exato momento do erro, o valor de SL/TP que você tentou enviar com o Bid/Ask atual.
- SL ou TP a uma distância menor que o stop level do preço atual → é praticamente essa a causa (causa ①).
- BUY com SL acima do Bid, ou TP abaixo do Bid (o inverso para SELL) → inversão de direção (causa ②).
- Só a modificação de posições abertas falha → freeze level ou especificação de SL/TP definido após a abertura (causas ③ e ⑤).
- O valor de SL é algo como "50", claramente não é um preço → confusão entre preço e pontos (causa ④).
Se quiser verificar via código, é uma linha só:
Print("StopsLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL),
" FreezeLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL));
Causas e soluções (6 padrões)
① SL/TP muito próximo do preço atual (abaixo do stop level)
Sintoma: frequente em EAs de scalping com SL apertado e EAs com trailing de faixa pequena. Mesmo manualmente, ao tentar colocar o SL "bem perto" do preço, o botão de envio trava ou a ordem é rejeitada.
Causa: a corretora define, para cada ativo, um SYMBOL_TRADE_STOPS_LEVEL (distância mínima de stop em pontos), e o servidor rejeita automaticamente qualquer SL/TP ou preço de ordem pendente a uma distância menor que esse valor do preço atual. O preço de referência para essa verificação é o Bid para SL/TP de posições BUY e o Ask para SELL. Quando o spread se alarga, a distância entre Bid e Ask aumenta, então uma distância que normalmente passa pode de repente ser rejeitada durante notícias econômicas ou no início da manhã.
Solução:
- Verifique o stop level na janela de especificação e amplie a configuração de SL/TP e da faixa de trailing do EA para ficar acima desse valor
- No lado do EA, aplique um "clamp" (ajuste ao mínimo permitido) antes de enviar a ordem (código abaixo)
- Se um SL muito apertado for realmente necessário, considere uma corretora ou tipo de conta com stop level menor
② Direção do SL/TP invertida (troca entre BUY/SELL)
Sintoma: ocorre sempre em uma direção específica (só compra ou só venda). É o padrão mais comum nos primeiros testes de um EA próprio.
Causa: a regra é simples — o SL de uma posição BUY deve ficar abaixo do preço atual (Bid) e o TP acima; o SL de uma SELL deve ficar acima do preço atual (Ask) e o TP abaixo. Erros típicos incluem copiar a fórmula de cálculo do BUY para o SELL e esquecer de inverter o sinal, ou trocar price - sl por price + sl. Nesses casos, o servidor retorna imediatamente 10016/130.
Solução:
- No momento do erro, use
PrintFormat("type=%s sl=%.5f tp=%.5f bid=%.5f ask=%.5f", ...)para exibir os valores reais e conferir visualmente a direção - Unifique o cálculo de SL/TP para BUY/SELL em uma função comum, centralizando a lógica de sinal em um único lugar (eliminando ramificações copiadas e coladas)
③ Modificação de ordem/posição dentro do freeze level
Sintoma: novas ordens funcionam normalmente, mas apenas a modificação/cancelamento pouco antes do TP, pouco antes do SL, ou pouco antes do disparo de uma pendente é rejeitada.
Causa: em ativos com SYMBOL_TRADE_FREEZE_LEVEL configurado, quando o preço atual se aproxima a uma certa distância do preço de disparo (TP/SL/gatilho de pendente), a modificação ou cancelamento dessa ordem fica congelado. É uma especificação do servidor para evitar conflito entre o processamento da execução e a requisição de modificação, e costuma se manifestar como um trailing do EA sendo rejeitado ao "tentar atualizar mais um degrau bem perto do TP".
Solução:
- Antes de modificar, leia
SYMBOL_TRADE_FREEZE_LEVELe, se a distância até o preço de disparo for menor ou igual ao freeze level, pule essa modificação - Aumente o intervalo e o passo de atualização do trailing, reduzindo requisições de modificação desnecessárias perto do disparo
- Como a rejeição não é fatal (perto do disparo significa que a execução está próxima), é aceitável projetar o tratamento para apenas registrar o erro no log sem interromper a execução
④ Confusão entre preço e pontos (distância)
Sintoma: o SL recebe um valor "pensado como distância", como 50 ou 0.0050, diretamente. O sl=50.00000 que aparece no log claramente não é um preço.
Causa: o que deve ser passado em MqlTradeRequest.sl / .tp é o preço absoluto (não "50 pontos abaixo da entrada"). EAs que gerenciam por distância precisam converter para entry ± distance * _Point antes de passar o valor. Por outro lado, se você passar uma distância onde deveria passar um preço absoluto — um hábito remanescente de algumas funções da era MT4 — o valor não é válido como preço e resulta em 10016/130.
Além disso, a confusão entre pips e pontos também é clássica. Em corretoras com 5 dígitos (por exemplo, USDJPY com 3 dígitos e EURUSD com 5 dígitos), 1 pip = 10 pontos. Dependendo se "SL 50" foi pensado como pips ou como pontos, a distância real muda em 10 vezes, podendo ficar abaixo do stop level e ser rejeitada.
Solução:
- Monte sempre o SL/TP no formato
NormalizeDouble(price ± dist * _Point, _Digits) - Documente claramente a unidade dos parâmetros de entrada (pips / pontos) em comentário e centralize a conversão de
_Pointem um único ponto do código
⑤ Corretoras com execução a mercado não aceitam SL/TP na abertura da ordem
Sintoma: a ordem é executada normalmente sem SL/TP, mas uma nova ordem com SL/TP retorna Invalid stops. Ocorre especialmente em contas ECN/execução a mercado (Market Execution).
Causa: na execução a mercado, como o "preço da requisição" e o "preço de execução real" podem divergir, alguns servidores não aceitam o SL/TP dentro da requisição de nova ordem, exigindo que ele seja definido via modificação de posição após a execução. É uma especificação clássica que já causava o erro 130 com frequência em contas ECN na era do MT4, e ainda existe em alguns servidores MT5 com o mesmo comportamento.
Solução:
- Mude para um esquema em duas etapas: primeiro envie a ordem sem SL/TP → após confirmar a execução, defina o SL/TP com
PositionModify()(outrade.PositionModify()no caso do CTrade) - Como esse método cria um momento em que "a ordem foi enviada com sucesso, mas a definição do SL falhou", é essencial implementar uma proteção que tente novamente até que a definição do SL seja aceita e feche a posição imediatamente após um número determinado de falhas (deixar a posição sem SL é o pior resultado possível)
- O modo de execução pode ser verificado com
SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE)
⑥ Particularidades específicas do ativo (ouro e índices têm stop level maior)
Sintoma: o mesmo EA funciona no EURUSD, mas ao usar em XAUUSD (ouro) ou CFDs de índices, passa a gerar Invalid stops repetidamente.
Causa: o stop level é configurado separadamente para cada ativo, e é comum que ouro, índices e moedas exóticas tenham valores maiores que as moedas principais. Se você usar diretamente o SL/faixa de trailing apertados ajustados para forex majors, a distância fica abaixo do mínimo exigido pelo ativo e é rejeitada. O número de dígitos também varia conforme o ativo (o ouro costuma ter 2 a 3 dígitos, por exemplo), então códigos que fixam _Digits como valor constante também quebram.
Solução:
- Ao mudar de ativo, sempre confira o stop level e o número de dígitos na janela de especificação
- Em vez de uma largura fixa em pontos para SL/TP, usar um critério de volatilidade como o ATR reduz a chance de falha ao mudar de ativo
- No código, obtenha sempre
_Point/_Digits/SYMBOL_TRADE_STOPS_LEVELdinamicamente (nunca fixe esses valores no código)
Diferenças entre corretoras (ponto de atenção)
Stop level e freeze level variam completamente conforme a combinação de corretora e ativo. É normal que o mesmo EA, com a mesma configuração, nunca apresente o erro na Corretora A, mas apresente diariamente na Corretora B.
Um ponto ainda mais importante é o das corretoras que exibem o stop level como "0". Na maioria das vezes, 0 não significa "sem limite", e sim "julgamento dinâmico" — em condições normais, até um SL bem próximo passa, mas é rejeitado justamente no momento em que o spread se alarga, como em divulgações de indicadores econômicos ou no início da manhã. É essa a causa mais comum por trás do "Invalid stops que só aparece de vez em quando".
Sempre confirme em uma conta real.
long stops = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); // em pontos
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); // em pontos
Como os valores específicos dependem do tipo de conta e da especificação do ativo de cada corretora, este artigo não vai listá-los. O único valor correto é o que você obtém executando o código acima na sua própria conta.
Código MQL5 para prevenir esse erro (para desenvolvedores de EA)
O design correto não é "corrigir quando o erro aparecer", e sim ajustar (clamp) o SL/TP à distância mínima da corretora antes de enviar a ordem, evitando que o 10016 sequer ocorra.
Validar e ajustar (clamp) o SL/TP antes de enviar a ordem
// Ajusta o SL/TP para uma distância igual ou maior que o stop level antes de enviar a ordem
// Retorno false = direção invertida (erro de design), portanto não envia a ordem
bool ClampStops(ENUM_ORDER_TYPE type, double &sl, double &tp)
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
long stopsPt = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double spread = SymbolInfoDouble(_Symbol, SYMBOL_ASK)
- SymbolInfoDouble(_Symbol, SYMBOL_BID);
// Margem de stop level + spread (proteção contra corretoras com julgamento dinâmico quando stops=0)
double minDist = stopsPt * point + spread;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(type == ORDER_TYPE_BUY)
{
// SL/TP de BUY são validados com base no Bid
if(sl > 0 && sl >= bid) return false; // direção invertida
if(tp > 0 && tp <= bid) return false;
if(sl > 0 && (bid - sl) < minDist) sl = bid - minDist;
if(tp > 0 && (tp - bid) < minDist) tp = bid + minDist;
}
else if(type == ORDER_TYPE_SELL)
{
// SL/TP de SELL são validados com base no Ask
if(sl > 0 && sl <= ask) return false; // direção invertida
if(tp > 0 && tp >= ask) return false;
if(sl > 0 && (sl - ask) < minDist) sl = ask + minDist;
if(tp > 0 && (ask - tp) < minDist) tp = ask - minDist;
}
sl = NormalizeDouble(sl, digits);
tp = NormalizeDouble(tp, digits);
return true;
}
Três pontos-chave:
- Obtenha
SYMBOL_TRADE_STOPS_LEVELeSYMBOL_POINTdinamicamente a cada vez (torna o código independente de ativo/corretora) - Adicione uma margem equivalente ao spread (facilita a aprovação mesmo em corretoras com julgamento dinâmico e stop level 0)
- Ao final, sempre use
NormalizeDouble(preço, _Digits)para ajustar as casas decimais (dígitos extras também podem causar rejeição)
Tratar o retcode 10016 individualmente
Registrar no log "o que foi enviado e qual era a distância naquele momento" quando a ordem é rejeitada permite identificar de imediato qual das causas ① a ⑥ está ocorrendo.
MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
// ... monte o req (sl/tp já ajustados via ClampStops) ...
if(!OrderSend(req, res) || res.retcode != TRADE_RETCODE_DONE)
{
if(res.retcode == TRADE_RETCODE_INVALID_STOPS) // 10016
PrintFormat("Invalid stops: sl=%s tp=%s bid=%s stopsLevel=%d",
DoubleToString(req.sl, _Digits),
DoubleToString(req.tp, _Digits),
DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits),
(int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL));
else
PrintFormat("OrderSend failed: retcode=%d, lastError=%d", res.retcode, GetLastError());
}
Só modifique o trailing depois de checar o freeze level
// Antes de modificar a posição, verifica se o preço de disparo está dentro da zona de freeze
bool CanModify(double triggerPrice)
{
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);
if(freeze <= 0) return true;
double dist = MathAbs(SymbolInfoDouble(_Symbol, SYMBOL_BID) - triggerPrice);
return (dist > freeze * SymbolInfoDouble(_Symbol, SYMBOL_POINT));
}
No caso de EAs na versão MT4, os mesmos valores podem ser obtidos com MarketInfo(Symbol(), MODE_STOPLEVEL) / MODE_FREEZELEVEL. O raciocínio é exatamente o mesmo.
Os EAs distribuídos pela FXEA365 já implementam como padrão essa validação de SL/TP antes do envio, o ajuste (clamp) ao stop level e a obtenção dinâmica independente de ativo, garantindo que não travem por Invalid stops mesmo ao trocar de corretora ou ativo.
Checklist de prioridades
| Prioridade | Verificação | Solução |
|---|---|---|
| 🚨 Primeiro | A distância entre SL/TP e o preço atual é menor que o stop level? | Ampliar a faixa de SL/TP / implementar clamp |
| 🚨 Primeiro | A direção do SL/TP está invertida entre BUY/SELL? | Exibir os valores reais no log e conferir visualmente |
| ⚠️ Em seguida | Só a modificação falha → está dentro do freeze level? | Pular a modificação próxima ao disparo |
| ⚠️ Em seguida | Está passando distância (em pontos) em vez de preço no sl/tp? | Converter para preço absoluto price ± dist*_Point |
| ✅ Verificar | É uma conta com execução a mercado que exige SL/TP após a abertura? | Esquema de duas etapas: enviar ordem → PositionModify |
| 🛠 Desenvolvimento | A especificação do ativo está sendo obtida dinamicamente? | Implementar o ClampStops acima |
Conclusão
Invalid stopsé 10016 (TRADE_RETCODE_INVALID_STOPS) no MT5 e 130 (ERR_INVALID_STOPS) no MT4. Os números são diferentes, mas o significado e o tratamento são os mesmos.- Na maioria dos casos, a causa não é um erro de cálculo, e sim uma das seis situações: distância abaixo do stop level, direção invertida, freeze level, confusão entre preço e pontos, especificação de SL/TP definido após a abertura, ou particularidade específica do ativo.
- Desenvolvedores podem resolver de forma permanente lendo
SYMBOL_TRADE_STOPS_LEVELantes de enviar a ordem, aplicando o clamp e usandoNormalizeDouble. Até corretoras com stop level 0 podem ser tratadas adicionando uma margem equivalente ao spread.
Para uma visão geral dos códigos de erro, consulte o Guia Completo de Códigos de Erro do MQL5/MT5. Sobre outro erro clássico de rejeição de ordem, "saldo insuficiente", veja o artigo sobre ERR_NO_MONEY (134/10019).
Perguntas Frequentes
P: O cálculo do SL/TP deveria estar correto, mas ainda assim aparece Invalid stops. Por quê?
É provável que a rejeição não tenha relação com a correção do valor, e sim com a distância em relação ao preço atual. Um SL/TP abaixo da distância mínima de stop (stop level) da corretora é rejeitado automaticamente, mesmo que o valor esteja matematicamente correto. Confirme a distância mínima na janela de especificação ou com SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL).
P: Qual é a diferença entre 10016 e 130?
10016 (TRADE_RETCODE_INVALID_STOPS) é o código de resultado do OrderSend() / PositionModify() do MT5, enquanto 130 (ERR_INVALID_STOPS) é o valor retornado pelo GetLastError() do MT4. A única diferença é a plataforma — o significado (posição inválida do SL/TP) e o tratamento são os mesmos.
P: Normalmente não acontece, mas o Invalid stops aparece só durante a divulgação de indicadores econômicos.
A causa é a ampliação do spread. Como o SL/TP de BUY é validado com base no Bid e o de SELL com base no Ask, a distância deixa de ser suficiente justamente no momento em que o spread se alarga. Mesmo em corretoras com stop level 0, pode haver um comportamento de rejeição dinâmica apenas em movimentos bruscos. Adicione uma margem equivalente ao spread na distância de SL/TP (veja o código no corpo do artigo).
P: A ordem passa sem SL/TP, mas é rejeitada quando inclui SL/TP.
É possível que se trate de uma conta com execução a mercado (Market Execution) cujo servidor não aceita SL/TP dentro da requisição de nova ordem. Mude para o esquema em duas etapas: enviar a ordem sem SL/TP e defini-lo com PositionModify() após a execução. No entanto, é essencial implementar uma nova tentativa caso a definição do SL falhe, além de uma proteção de fechamento imediato caso as falhas persistam.
P: Um EA que funciona no EURUSD gera Invalid stops repetidamente no ouro.
É normal que ouro e índices de ações tenham um stop level maior que as moedas principais, além de um número de dígitos diferente. Amplie a faixa de SL/trailing de acordo com a especificação do ativo, ou mude para uma faixa vinculada à volatilidade com base em um critério como o ATR.
Relacionados
📧 Avisos antes de aumentos de preço + curso gratuito de 5 dias por e-mail
Todos os EAs estão em preço de lançamento e sobem em degraus conforme as vendas. Receba aviso antes de cada aumento, além de um e-mail diário sobre trading algorítmico, leitura de backtests e escolha de corretora.
* Privacidade rigorosamente protegida. Você pode cancelar a inscrição a qualquer momento.