Accueil > Blog > Invalid stops (10016/130) — le SL sur MT5/MT4

MT5MQL5ErreurRésolution de problèmesEA

Invalid stops (10016/130) — le SL sur MT5/MT4

Publié : 2026-07-07Lecture : env. 9 min
This article reflects information as of its publish date. EA performance figures (PF, DD, annual return) change with live trading and re-validation — check the latest on the EA pages. See the latest EA results

Résoudre définitivement Invalid stops (10016/130)

Quand un EA tourne et que l'onglet Experts affiche Invalid stops ou OrderSend error 130, on a tendance à penser « le SL est mal calculé ? Mais le calcul devrait être correct... ». Pourtant, la grande majorité de cette erreur ne vient pas d'une erreur de calcul du SL/TP, mais d'une violation de la règle de distance minimale imposée par le broker. Même si la valeur elle-même est correcte, elle est rejetée si elle est trop proche du prix actuel, si le sens est inversé, ou si elle tombe dans une zone où la modification est interdite.

Cet article s'adresse à la fois aux utilisateurs d'EA sur MT5/MT4 et aux développeurs d'EA en MQL5. Il rassemble en un seul guide de référence la nature de l'erreur Invalid stops, ses 6 causes, un diagnostic en 30 secondes et des solutions permanentes côté code. Pour la liste complète des codes d'erreur, consultez le guide complet de résolution des codes d'erreur MQL5/MT5.

Cet article se base sur MT5 (série build 4xxx) à la date de juillet 2026. Les valeurs précises des stop levels varient selon le broker et l'instrument.


Qu'est-ce qu'Invalid stops (différence entre 10016 et 130) ?

La valeur représentant un « stop invalide » existe en deux versions selon MT5 et MT4 (génération). Identifier sur quelle plateforme et à quelle étape elle apparaît accélère considérablement le diagnostic.

① TRADE_RETCODE_INVALID_STOPS = 10016 (MT5 / code de retour d'OrderSend())

Le résultat de OrderSend() en MQL5 est stocké dans MqlTradeResult.retcode. Si le SL/TP de la requête (ou sa relation avec le prix d'un ordre en attente) ne respecte pas les règles du serveur, elle est rejetée avec le code 10016 (TRADE_RETCODE_INVALID_STOPS). Il s'agit d'une notification de rejet émise par le serveur de trading.

Signification : Stop invalide (SL/TP) dans la requête (Invalid stops in the request)
Constante     : TRADE_RETCODE_INVALID_STOPS
Valeur        : 10016
// Exemple typique visible dans les logs
2026.07.07 09:15:32.441 EA_NAME XAUUSD,M5: OrderSend error: retcode=10016 (invalid stops)

② ERR_INVALID_STOPS = 130 (MT4 / GetLastError())

Sur la génération MT4 (MQL4), GetLastError() retourne 130 (ERR_INVALID_STOPS) après l'échec de OrderSend() / OrderModify(). C'est ce qui correspond à OrderSend error 130 dans l'onglet Experts. Ce code apparaît si un EA version MT4 est utilisé en parallèle.

Signification : Stop invalide (invalid stops)
Constante     : ERR_INVALID_STOPS
Valeur        : 130

Récapitulatif pratique :

PlateformeSourceValeurContexte d'apparition
MT5MqlTradeResult.retcode10016 (TRADE_RETCODE_INVALID_STOPS)OrderSend / PositionModify rejeté par le serveur
MT5CTrade.ResultRetcode()10016Rejet d'un envoi/modification via CTrade
MT4GetLastError()130 (ERR_INVALID_STOPS)Après échec de OrderSend / OrderModify

Les numéros diffèrent, mais la signification et la cause sont quasiment identiques, et la solution est commune aux deux. À noter que le code 10015 (TRADE_RETCODE_INVALID_PRICE), souvent confondu avec 10016 sur MT5, signifie « le prix de l'ordre lui-même est invalide » — c'est un cas distinct. Le code 10016 concerne uniquement la position du SL/TP (le stop).


Diagnostic rapide en 30 secondes

Ouvrez « Cotations → clic droit sur le symbole → Spécification » sur MT5 et vérifiez ces deux éléments :

Stop level (Niveau de stop)  : distance minimale (en points) requise entre le SL/TP et le prix actuel
Freeze level (Niveau de gel) : distance (en points) en dessous de laquelle la modification/annulation d'un ordre proche du déclenchement est interdite

Ensuite, comparez dans les logs, au moment de l'erreur, la valeur du SL/TP envoyée et le Bid/Ask actuel.

  • Le SL ou le TP se trouve à une distance du prix actuel inférieure au stop level → c'est presque toujours la cause (cause ①).
  • En BUY, le SL est au-dessus du Bid ou le TP en dessous (inverse pour un SELL) → erreur de sens (cause ②).
  • Seule la modification d'une position ouverte échoue → freeze level ou spécificité d'ajout du SL/TP après coup (causes ③ et ⑤).
  • La valeur du SL est un nombre qui n'est manifestement pas un prix, comme « 50 » → confusion entre prix et points (cause ④).

Pour vérifier directement depuis le code, une seule ligne suffit :

Print("StopsLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL),
      " FreezeLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL));

Causes et solutions (6 cas)

① Le SL/TP est trop proche du prix actuel (inférieur au stop level)

Symptôme : fréquent avec les EA de scalping qui placent un SL serré ou dont l'écart de trailing est faible. Même manuellement, si l'on tente de placer un SL « juste à côté » du prix, le bouton d'ordre reste bloqué ou l'ordre est rejeté.

Cause : le broker définit, pour chaque instrument, SYMBOL_TRADE_STOPS_LEVEL (distance minimale du stop, en points). Tout SL/TP ou prix d'ordre en attente situé à une distance inférieure à cette valeur par rapport au prix actuel est systématiquement rejeté par le serveur. Le prix de référence pour la vérification est le Bid pour le SL/TP d'une position BUY, et l'Ask pour une position SELL. Lorsque le spread s'élargit, l'écart entre Bid et Ask augmente : une distance qui passe habituellement peut donc soudainement être rejetée lors d'une publication de données économiques ou tôt le matin.

Solution :

  1. Vérifiez le stop level dans la fenêtre de spécification et élargissez le SL/TP ou l'écart de trailing de l'EA pour qu'il le dépasse
  2. Côté EA, appliquez un clamp avant l'envoi de l'ordre (code ci-dessous)
  3. Si un SL très serré est absolument nécessaire, envisagez un broker ou un type de compte avec un stop level plus faible

② Le sens du SL/TP est inversé (confusion BUY/SELL)

Symptôme : l'erreur Invalid stops se produit systématiquement dans une direction précise (achat uniquement ou vente uniquement). C'est le cas le plus fréquent lors des premiers tests d'un EA fait maison.

Cause : la règle est simple : pour un BUY, le SL doit être en dessous du prix actuel (Bid) et le TP au-dessus ; pour un SELL, le SL doit être au-dessus du prix actuel (Ask) et le TP en dessous. Une erreur classique consiste à copier la formule de calcul du BUY vers le SELL en oubliant d'inverser le signe, ou à confondre price - sl et price + sl. Le serveur renvoie alors immédiatement 10016/130.

Solution :

  1. En cas d'erreur, affichez les valeurs réelles avec PrintFormat("type=%s sl=%.5f tp=%.5f bid=%.5f ask=%.5f", ...) pour vérifier visuellement le sens
  2. Regroupez le calcul du SL/TP pour BUY et SELL dans une fonction commune, avec la logique de signe centralisée en un seul endroit (éliminez les branches copiées-collées)

③ Modification d'un ordre ou d'une position à l'intérieur du freeze level

Symptôme : l'ouverture d'un nouvel ordre fonctionne, mais seule la modification ou l'annulation juste avant l'atteinte du TP, du SL, ou du déclenchement d'un ordre en attente est rejetée.

Cause : sur les instruments où SYMBOL_TRADE_FREEZE_LEVEL est défini, la modification ou l'annulation d'un ordre est gelée dès que le prix actuel s'approche à une certaine distance du prix de déclenchement (TP/SL ou prix de déclenchement d'un ordre en attente). C'est une spécificité du serveur destinée à éviter les conflits entre l'exécution de l'ordre et la requête de modification. Ce comportement se manifeste souvent lorsque le trailing d'un EA tente une mise à jour supplémentaire juste avant l'atteinte du TP, et se fait rejeter.

Solution :

  1. Avant toute modification, lisez SYMBOL_TRADE_FREEZE_LEVEL et ignorez la modification si la distance jusqu'au prix de déclenchement est inférieure ou égale au freeze level
  2. Élargissez l'intervalle et l'amplitude de mise à jour du trailing pour réduire les requêtes de modification inutiles juste avant le déclenchement
  3. Un rejet dans ce cas n'est pas critique (proche du déclenchement signifie une exécution imminente) : il est acceptable de simplement ignorer l'erreur en la consignant dans les logs

④ Confusion entre prix et points (distance)

Symptôme : une valeur destinée à représenter une distance, comme 50 ou 0.0050, est directement insérée dans le SL. La valeur sl=50.00000 visible dans les logs n'est manifestement pas un prix.

Cause : les champs .sl / .tp de MqlTradeRequest attendent un prix absolu (et non « 50 points en dessous de l'entrée »). Un EA qui gère les distances doit d'abord les convertir via entry ± distance * _Point avant de les transmettre. À l'inverse, si l'on transmet une distance là où un prix absolu est attendu, par habitude héritée de certaines fonctions de l'époque MT4, la valeur ne constitue pas un prix valide et provoque 10016/130.

Autre confusion classique : pips et points. Sur un broker à 5 chiffres (par exemple USDJPY affiché en 3 chiffres, EURUSD en 5 chiffres), 1 pip = 10 points. Si « SL 50 » est censé représenter des pips mais est interprété comme des points (ou l'inverse), la distance réelle est décalée d'un facteur 10 et peut tomber sous le stop level, entraînant un rejet.

Solution :

  1. Construisez toujours le SL/TP sous la forme NormalizeDouble(price ± dist * _Point, _Digits)
  2. Précisez clairement l'unité des paramètres d'entrée (pips / points) en commentaire, et centralisez la conversion via _Point en un seul endroit du code

⑤ Le broker en exécution au marché refuse le SL/TP à l'ouverture d'une position

Symptôme : l'ordre passe sans SL/TP, mais l'ouverture d'une nouvelle position avec SL/TP renvoie systématiquement Invalid stops. Ce cas se produit surtout sur les comptes ECN ou en exécution au marché (Market Execution).

Cause : en exécution au marché, le prix demandé et le prix réellement exécuté peuvent différer. Certains serveurs refusent donc le SL/TP inclus dans la requête d'ouverture et exigent qu'il soit défini après exécution, via une modification de la position (une spécificité classique héritée de l'époque des comptes ECN sur MT4, où l'erreur 130 était fréquente, et qui persiste sur certains serveurs MT5).

Solution :

  1. Adoptez une approche en deux étapes : envoyez d'abord l'ordre sans SL/TP, puis, une fois l'exécution confirmée, ajoutez le SL/TP via PositionModify() (ou trade.PositionModify() avec CTrade)
  2. Cette méthode crée un instant où « l'ordre a réussi mais la définition du SL a échoué » : prévoyez impérativement une protection qui réessaie jusqu'à ce que le SL soit accepté, et clôture immédiatement la position après un nombre défini d'échecs (laisser une position sans SL est le pire scénario)
  3. Le mode d'exécution peut être vérifié via SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE)

⑥ Particularités propres à certains instruments (l'or et les indices ont des stop levels plus élevés)

Symptôme : le même EA fonctionne sur EURUSD, mais dès qu'il est appliqué sur XAUUSD (or) ou un CFD sur indice boursier, l'erreur Invalid stops se répète.

Cause : le stop level est défini indépendamment pour chaque instrument, et l'or, les indices ainsi que les devises exotiques ont généralement un stop level plus élevé que les devises majeures. Réutiliser tel quel un SL ou un écart de trailing serré, calibré pour le forex majeur, ne permet pas d'atteindre la distance minimale exigée par l'instrument et provoque un rejet. Le nombre de décimales varie également selon l'instrument (l'or utilise par exemple 2 ou 3 décimales), ce qui casse également un code où _Digits est codé en dur.

Solution :

  1. À chaque changement d'instrument, vérifiez systématiquement le stop level et le nombre de décimales dans la fenêtre de spécification
  2. Basez l'amplitude du SL/TP sur un indicateur de volatilité comme l'ATR plutôt que sur un nombre de points fixe, afin de mieux résister au changement d'instrument
  3. Le code doit toujours récupérer dynamiquement _Point, _Digits et SYMBOL_TRADE_STOPS_LEVEL (jamais de valeurs codées en dur)

Différences selon les brokers (points d'attention)

Le stop level et le freeze level varient totalement selon la combinaison broker/instrument. Avec le même EA et les mêmes réglages, il est tout à fait courant qu'une erreur qui n'apparaît jamais chez le broker A survienne quotidiennement chez le broker B.

Autre point d'attention important : les brokers où le stop level s'affiche à « 0 ». Ce 0 ne signifie pas « aucune limite », mais indique le plus souvent une évaluation dynamique : en temps normal, même un SL très proche passe sans problème, mais il est rejeté uniquement au moment où le spread s'élargit, comme lors d'une publication économique ou tôt le matin. C'est généralement l'origine des erreurs « Invalid stops qui n'apparaissent que de temps en temps ».

Vérifiez toujours cela directement sur votre compte réel.

long stops  = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);   // en points
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL);  // en points

Les valeurs précises dépendent du type de compte et de la spécification de chaque instrument propre à chaque broker : elles ne sont donc pas indiquées dans cet article. La seule valeur de référence valable est celle obtenue en exécutant le code ci-dessus sur votre propre compte.


Code MQL5 pour prévenir cette erreur (pour les développeurs d'EA)

La bonne approche n'est pas de « corriger après l'apparition de l'erreur », mais de clamper le SL/TP à la distance minimale du broker avant l'envoi de l'ordre, afin d'éviter que le code 10016 ne se produise.

Vérifier et clamper le SL/TP avant l'envoi de l'ordre

// Clampe le SL/TP à une distance au moins égale au stop level avant d'envoyer l'ordre
// Valeur de retour false = sens inversé (erreur de conception) : ne pas envoyer l'ordre
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);
   // Marge = stop level + spread (protection contre les brokers à évaluation dynamique où stops=0)
   double minDist = stopsPt * point + spread;

   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   if(type == ORDER_TYPE_BUY)
   {
      // Le SL/TP d'un BUY est vérifié par rapport au Bid
      if(sl > 0 && sl >= bid) return false;              // sens inversé
      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)
   {
      // Le SL/TP d'un SELL est vérifié par rapport à l'Ask
      if(sl > 0 && sl <= ask) return false;              // sens inversé
      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;
}

Trois points essentiels :

  1. Récupérer dynamiquement SYMBOL_TRADE_STOPS_LEVEL et SYMBOL_POINT à chaque fois (rend le code indépendant de l'instrument et du broker)
  2. Ajouter une marge correspondant au spread (facilite le passage même chez les brokers à évaluation dynamique avec un stop level à 0)
  3. Toujours terminer par NormalizeDouble(prix, _Digits) pour aligner le nombre de décimales (des décimales superflues peuvent aussi entraîner un rejet)

Gérer spécifiquement le retcode 10016

En consignant dans les logs, au moment du rejet, ce qui a été envoyé et quelle était la distance à cet instant, on identifie immédiatement laquelle des causes ①-⑥ est en jeu.

MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
// ... construction de req (sl/tp déjà passés par 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());
}

Vérifier le freeze level avant toute modification du trailing

// Avant de modifier une position, vérifier si le prix de déclenchement se trouve dans la zone de gel
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));
}

Pour un EA version MT4, les mêmes valeurs sont accessibles via MarketInfo(Symbol(), MODE_STOPLEVEL) / MODE_FREEZELEVEL. Le principe est rigoureusement identique.

Les EA distribués par FXEA365 implémentent en standard cette vérification et ce clamp du SL/TP avant l'envoi de l'ordre, ainsi que la récupération dynamique des spécifications indépendante de l'instrument, ce qui leur permet de continuer à fonctionner sans être bloqués par Invalid stops, quel que soit le broker ou l'instrument utilisé.


Liste de contrôle par priorité

PrioritéVérificationSolution
🚨 D'abordLa distance entre le SL/TP et le prix actuel est-elle < au stop level ?Élargir le SL/TP / implémenter un clamp
🚨 D'abordLe sens du SL/TP est-il inversé entre BUY et SELL ?Afficher les valeurs réelles dans les logs et vérifier visuellement
⚠️ EnsuiteSeule la modification échoue → est-ce lié au freeze level ?Ignorer les modifications juste avant le déclenchement
⚠️ EnsuiteUne distance (en points) est-elle insérée dans sl/tp ?Convertir en prix absolu price ± dist*_Point
✅ À vérifierLe compte nécessite-t-il un ajout du SL/TP après exécution au marché ?Approche en deux étapes : envoi puis PositionModify
🛠 DéveloppementLes spécifications de l'instrument sont-elles récupérées dynamiquement ?Implémenter la fonction ClampStops ci-dessus

Résumé

  • Invalid stops correspond au code 10016 (TRADE_RETCODE_INVALID_STOPS) sur MT5 et au code 130 (ERR_INVALID_STOPS) sur MT4. Les numéros diffèrent, mais la signification et la solution sont communes.
  • La grande majorité des causes ne vient pas d'une erreur de calcul, mais de l'un de ces 6 cas : distance inférieure au stop level, sens inversé, freeze level, confusion entre prix et points, spécificité d'ajout du SL/TP après coup, particularité propre à l'instrument.
  • Les développeurs peuvent apporter une solution permanente en lisant SYMBOL_TRADE_STOPS_LEVEL avant l'envoi de l'ordre, en appliquant un clamp, puis un NormalizeDouble. Même chez les brokers avec un stop level à 0, une marge correspondant au spread permet d'absorber le problème.

Pour la liste complète des codes d'erreur, consultez le guide complet de résolution des codes d'erreur MQL5/MT5. Une autre erreur classique de rejet d'ordre, le manque de fonds, est traitée dans l'article dédié à ERR_NO_MONEY (134/10019).


FAQ

Q : Le calcul de mon SL/TP devrait être correct, mais j'obtiens quand même Invalid stops. Pourquoi ?

Il est probable que le rejet ne vienne pas d'une valeur incorrecte, mais de sa distance par rapport au prix actuel. Un SL/TP situé sous la distance minimale de stop imposée par le broker (stop level) est systématiquement rejeté, même si la valeur est exacte. Vérifiez la distance minimale dans la fenêtre de spécification ou via SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL).

Q : Quelle est la différence entre 10016 et 130 ?

10016 (TRADE_RETCODE_INVALID_STOPS) est le code de résultat renvoyé par OrderSend() / PositionModify() sur MT5, tandis que 130 (ERR_INVALID_STOPS) est la valeur renvoyée par GetLastError() sur MT4. Seule la plateforme diffère : la signification (position du SL/TP invalide) et la solution sont identiques.

Q : L'erreur Invalid stops n'apparaît habituellement jamais, mais survient uniquement lors des publications économiques.

C'est l'élargissement du spread qui en est la cause. Le SL/TP d'un BUY est vérifié par rapport au Bid, celui d'un SELL par rapport à l'Ask : dès que le spread s'élargit, la distance devient insuffisante à cet instant précis. Même chez un broker avec un stop level à 0, un rejet dynamique peut survenir uniquement lors de mouvements brusques. Ajoutez une marge correspondant au spread à la distance du SL/TP (voir le code dans l'article).

Q : L'ordre passe sans SL/TP, mais est rejeté dès que j'ajoute un SL/TP.

Il est possible que le serveur, sur un compte en exécution au marché (Market Execution), refuse le SL/TP inclus dans la requête d'ouverture. Passez à une approche en deux étapes : envoyer l'ordre sans SL/TP, puis le définir via PositionModify() une fois l'exécution confirmée. Prévoyez impérativement une logique de réessai en cas d'échec de la définition du SL, ainsi qu'une protection de clôture immédiate en cas d'échecs répétés.

Q : Un EA qui fonctionne sur EURUSD déclenche en boucle Invalid stops sur l'or.

Il est normal que l'or et les indices boursiers aient un stop level plus élevé que les devises majeures, avec également un nombre de décimales différent. Élargissez le SL et l'écart de trailing en fonction des spécifications de l'instrument, ou passez à un écart lié à la volatilité, basé sur l'ATR.

📧 Alertes avant hausse de prix + cours gratuit de 5 jours par e-mail

Tous les EA sont au prix de lancement et augmentent par paliers selon les ventes. Soyez averti avant chaque hausse, plus un e-mail quotidien sur le trading algo, la lecture des backtests et le choix du courtier.

* Confidentialité strictement protégée. Vous pouvez vous désabonner à tout moment.

Commentaires et questions