Ошибка Invalid stops (10016/130) — SL в MT5/MT4
Содержание
- Что такое Invalid stops (разница между 10016 и 130)
- ① TRADE_RETCODE_INVALID_STOPS = 10016 (MT5 / код возврата `OrderSend()`)
- ② ERR_INVALID_STOPS = 130 (MT4 / `GetLastError()`)
- Быстрая 30-секундная диагностика
- Причины и способы устранения (6 сценариев)
- ① SL/TP слишком близко к текущей цене (меньше уровня стопов)
- ② Направление SL/TP перепутано (BUY/SELL наоборот)
- ③ Изменение ордера или позиции внутри зоны заморозки
- ④ Путаница между ценой и пунктами (расстоянием)
- ⑤ У брокеров с рыночным исполнением SL/TP не принимаются при открытии позиции
- ⑥ Особенности конкретных инструментов (у золота и индексов уровень стопов выше)
- Различия между брокерами (на что обратить внимание)
- Код на MQL5 для предотвращения этой ошибки (для разработчиков советников)
- Проверка и ограничение SL/TP перед отправкой ордера
- Отдельная обработка retcode 10016
- Проверяйте уровень заморозки перед изменением трейлинга
- Чек-лист по приоритетам
- Заключение
- Частые вопросы
- В: Расчёт SL/TP должен быть верным, но всё равно появляется Invalid stops. Почему?
- В: В чём разница между 10016 и 130?
- В: Обычно ошибки нет, но Invalid stops появляется только во время выхода новостей.
- В: Ордер без SL/TP проходит, а с указанными SL/TP отклоняется.
- В: Советник нормально работает на EURUSD, но на золоте постоянно выдаёт Invalid stops.
Полное решение ошибки Invalid stops (10016/130)
Когда во вкладке «Эксперты» при работе советника появляется Invalid stops или OrderSend error 130, легко растеряться: «Значение SL неверное? Но расчёт вроде правильный…» На самом деле в большинстве случаев причина не в ошибке расчёта SL/TP, а в нарушении «правила минимальной дистанции», установленного брокером. Само значение может быть корректным, но оно отклоняется, если находится слишком близко к текущей цене, направлено в обратную сторону или попадает в зону, запрещённую для изменения.
Эта статья предназначена как для тех, кто использует советники в MT5/MT4, так и для тех, кто пишет EA на MQL5. Это исчерпывающее руководство охватывает суть ошибки Invalid stops, 6 её причин, 30-секундную диагностику и постоянную защиту на уровне кода. Полный список кодов ошибок смотрите в сводном руководстве по устранению ошибок MQL5/MT5.
Статья актуальна для MT5 (сборки серии 4xxx) по состоянию на июль 2026 года. Конкретные значения уровня стопов зависят от брокера и торгового инструмента.
Что такое Invalid stops (разница между 10016 и 130)
Значение, обозначающее «недопустимый стоп», существует в двух вариантах — для MT5 и для MT4 (более старого поколения). Если сразу определить, на какой платформе и на каком этапе возникла ошибка, поиск причины пойдёт значительно быстрее.
① TRADE_RETCODE_INVALID_STOPS = 10016 (MT5 / код возврата OrderSend())
Результат выполнения OrderSend() в MQL5 записывается в MqlTradeResult.retcode. Если SL/TP в запросе (или их соотношение с ценой отложенного ордера) не соответствует правилам сервера, запрос отклоняется с кодом 10016 (TRADE_RETCODE_INVALID_STOPS). Это уведомление об отказе со стороны торгового сервера.
Значение: недопустимые стопы в запросе (Invalid stops in the request)
Константа: TRADE_RETCODE_INVALID_STOPS
Значение : 10016
// Типичный пример записи в журнале
2026.07.07 09:15:32.441 EA_NAME XAUUSD,M5: OrderSend error: retcode=10016 (invalid stops)
② ERR_INVALID_STOPS = 130 (MT4 / GetLastError())
В поколении MT4 (MQL4) после неудачного вызова OrderSend() / OrderModify() функция GetLastError() возвращает код 130 (ERR_INVALID_STOPS). Именно это отображается как OrderSend error 130 во вкладке «Эксперты». Если вы одновременно используете советник на MT4, увидите именно этот код.
Значение: недопустимые стопы (invalid stops)
Константа: ERR_INVALID_STOPS
Значение : 130
Практическое разграничение:
| Платформа | Источник | Значение | Когда возникает |
|---|---|---|---|
| MT5 | MqlTradeResult.retcode | 10016 (TRADE_RETCODE_INVALID_STOPS) | Сервер отклонил OrderSend / PositionModify |
| MT5 | CTrade.ResultRetcode() | 10016 | Отказ при выставлении/изменении ордера через CTrade |
| MT4 | GetLastError() | 130 (ERR_INVALID_STOPS) | После неудачного OrderSend / OrderModify |
Номера разные, но смысл и причины практически одинаковы, и способы устранения тоже общие. Отдельно стоит отметить: часто путаемый с этой ошибкой код 10015 (TRADE_RETCODE_INVALID_PRICE) в MT5 означает «недопустима сама цена ордера» — это другая проблема. 10016 связана исключительно с положением SL/TP (стопов).
Быстрая 30-секундная диагностика
Откройте в MT5 «Обзор рынка → правый клик по символу → Спецификация» и посмотрите на два параметра:
Уровень стопов (Stops level) : минимальное расстояние (в пунктах) от текущей цены до SL/TP
Уровень заморозки (Freeze level): расстояние (в пунктах), при котором запрещено изменять/удалять ордер, близкий к исполнению
После этого сравните в журнале, зафиксированном в момент ошибки, значения SL/TP, которые вы пытались отправить, с текущими Bid/Ask.
- SL или TP находятся на расстоянии от текущей цены меньше уровня стопов → почти наверняка это причина (причина ①).
- При BUY значение SL выше Bid либо TP ниже Bid (при SELL — наоборот) → перепутано направление (причина ②).
- Не проходит только изменение уже открытой позиции → уровень заморозки либо специфика брокера, требующая установки SL/TP отдельным шагом (причины ③ и ⑤).
- Значение SL похоже на «50» — явно не похоже на цену → путаница между ценой и пунктами (причина ④).
Проверить это из кода можно одной строкой:
Print("StopsLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL),
" FreezeLevel=", SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL));
Причины и способы устранения (6 сценариев)
① SL/TP слишком близко к текущей цене (меньше уровня стопов)
Симптом: часто встречается у скальперских советников с плотным SL и у EA с небольшим шагом трейлинга. При ручной торговле — если попытаться поставить SL «прямо у цены», кнопка отправки ордера не срабатывает или запрос отклоняется.
Причина: брокер задаёт для каждого символа параметр SYMBOL_TRADE_STOPS_LEVEL (минимальная дистанция стопа в пунктах), и любые SL/TP или цены отложенных ордеров ближе этого расстояния сервер отклоняет автоматически. Базовой ценой для проверки служит Bid для SL/TP позиции BUY и Ask для SELL. При расширении спреда разрыв между Bid и Ask увеличивается, поэтому дистанция, которая проходит в обычное время, может внезапно быть отклонена во время выхода новостей или ранним утром.
Решение:
- Проверьте уровень стопов в окне спецификации и увеличьте настройки SL/TP и ширины трейлинга советника так, чтобы они превышали это значение
- На стороне EA выполняйте ограничение (клампинг) значений перед отправкой ордера (код приведён ниже)
- Если критически необходим плотный SL, рассмотрите брокера или тип счёта с меньшим уровнем стопов
② Направление SL/TP перепутано (BUY/SELL наоборот)
Симптом: ошибка Invalid stops возникает стабильно только в одном направлении (только на покупку либо только на продажу). Самая частая ошибка при первом тестировании самописного EA.
Причина: правило простое — для BUY значение SL должно быть ниже текущей цены (Bid), а TP выше; для SELL значение SL должно быть выше текущей цены (Ask), а TP ниже. Типичная ошибка — скопировать формулу для BUY в блок SELL и забыть поменять знак, либо перепутать price - sl и price + sl. Сервер в таком случае немедленно возвращает 10016/130.
Решение:
- При возникновении ошибки выводите реальные значения через
PrintFormat("type=%s sl=%.5f tp=%.5f bid=%.5f ask=%.5f", ...)и визуально проверяйте направление - Вынесите расчёт SL/TP для BUY и SELL в общую функцию, сведя разветвление по знаку в одно место (избавьтесь от копипасты с развилками)
③ Изменение ордера или позиции внутри зоны заморозки
Симптом: новые ордера проходят без проблем, но отклоняется только изменение/отмена непосредственно перед срабатыванием TP, SL или отложенного ордера.
Причина: для символов, у которых задан SYMBOL_TRADE_FREEZE_LEVEL, при приближении текущей цены к цене срабатывания (TP/SL/цене триггера отложенного ордера) на определённое расстояние изменение или отмена этого ордера блокируется. Это серверное ограничение, предотвращающее конфликт между исполнением ордера и запросом на его изменение. На практике это проявляется так: трейлинг советника пытается сделать ещё одно обновление прямо перед достижением TP — и получает отказ.
Решение:
- Перед изменением считывайте
SYMBOL_TRADE_FREEZE_LEVELи, если расстояние до цены срабатывания меньше или равно уровню заморозки, пропускайте изменение в этом цикле - Увеличьте интервал и шаг обновления трейлинга, чтобы сократить число бесполезных запросов на изменение перед самым срабатыванием
- Такой отказ не критичен (раз ордер близок к срабатыванию — значит, скоро исполнится сам), поэтому вполне допустимо просто игнорировать ошибку, оставляя запись только в журнале
④ Путаница между ценой и пунктами (расстоянием)
Симптом: в поле SL напрямую передаётся значение, задуманное как «расстояние», например 50 или 0.0050. В журнале явно видно, что sl=50.00000 — это не похоже на цену.
Причина: в MqlTradeRequest.sl / .tp необходимо передавать абсолютную цену (а не «50 пунктов ниже входа»). Если советник оперирует расстоянием, его нужно преобразовать в entry ± distance * _Point перед передачей. Обратная ошибка — по привычке из некоторых функций эпохи MT4 передавать расстояние туда, где ожидается абсолютная цена: в результате значение не является корректной ценой, и возникает 10016/130.
Ещё одна классическая ошибка — путаница пунктов (points) и пипсов (pips). У 5-значных брокеров (например, USDJPY отображается с 3 знаками, EURUSD — с 5) 1 пипс = 10 пунктов. Если значение «SL 50» задумывалось как пипсы, а трактуется как пункты (или наоборот), расстояние отличается в 10 раз, что может опустить его ниже уровня стопов и привести к отказу.
Решение:
- Всегда формируйте SL/TP в виде
NormalizeDouble(price ± dist * _Point, _Digits) - Явно указывайте единицы измерения входных параметров (пипсы / пункты) в комментариях и сводите преобразование через
_Pointв одно место кода
⑤ У брокеров с рыночным исполнением SL/TP не принимаются при открытии позиции
Симптом: ордер без SL/TP исполняется успешно, а новый ордер с указанными SL/TP выдаёт Invalid stops. Особенно часто встречается на счетах ECN / Market Execution.
Причина: при рыночном исполнении цена в момент запроса и фактическая цена исполнения могут отличаться, поэтому некоторые серверы не принимают SL/TP непосредственно в запросе нового ордера и требуют устанавливать их отдельным изменением позиции уже после исполнения (классическое поведение ECN-счетов эпохи MT4, где часто возникала ошибка 130; в MT5 подобное поведение сохраняется у ряда серверов).
Решение:
- Перейдите на двухэтапную схему: сначала отправляйте ордер без SL/TP → после подтверждения исполнения устанавливайте SL/TP через
PositionModify()(илиtrade.PositionModify()при использовании CTrade) - При таком подходе возникает момент, когда «ордер исполнен, но установка SL не удалась» — обязательно предусмотрите защиту: повторяйте попытку установки SL, а после определённого числа неудач немедленно закрывайте позицию (оставлять позицию без SL — худший из возможных исходов)
- Режим исполнения можно проверить через
SymbolInfoInteger(_Symbol, SYMBOL_TRADE_EXEMODE)
⑥ Особенности конкретных инструментов (у золота и индексов уровень стопов выше)
Симптом: один и тот же советник нормально работает на EURUSD, но при переносе на XAUUSD (золото) или CFD на фондовый индекс начинает постоянно выдавать Invalid stops.
Причина: уровень стопов задаётся индивидуально для каждого инструмента, и у золота, индексов и экзотических валют он обычно значительно выше, чем у основных валютных пар. Если использовать плотные значения SL/трейлинга, настроенные под мажорные пары, без изменений, они не дотягивают до минимальной дистанции инструмента и отклоняются. Количество знаков после запятой также различается между инструментами (у золота, например, 2–3 знака), поэтому код с жёстко заданным _Digits тоже может ломаться.
Решение:
- При смене инструмента обязательно проверяйте уровень стопов и количество знаков в окне спецификации
- Задавайте ширину SL/TP не фиксированным числом пунктов, а на основе волатильности (например, ATR) — так настройки будут устойчивее при переходе между инструментами
- Всегда получайте
_Point/_Digits/SYMBOL_TRADE_STOPS_LEVELдинамически из кода (никаких жёстко заданных значений)
Различия между брокерами (на что обратить внимание)
Уровень стопов и уровень заморозки полностью различаются в зависимости от комбинации брокера и инструмента. Совершенно нормальная ситуация — когда один и тот же советник с одинаковыми настройками ни разу не выдаёт ошибку у брокера A, но выдаёт её каждый день у брокера B.
Особое внимание стоит обратить на брокеров, у которых уровень стопов отображается как «0». Ноль часто означает не «ограничений нет», а «оценивается динамически»: в обычное время проходит SL на любом расстоянии, но в момент выхода новостей или ранним утром, когда спред расширяется, запрос внезапно отклоняется. За «редко возникающей» ошибкой Invalid stops чаще всего скрывается именно это.
Проверку обязательно проводите на реальном счёте.
long stops = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); // пункты
long freeze = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); // пункты
Конкретные числовые значения зависят от типа счёта и спецификации инструмента у каждого брокера, поэтому в этой статье они не приводятся. Единственно верный ответ — значение, полученное выполнением приведённого кода на вашем собственном счёте.
Код на MQL5 для предотвращения этой ошибки (для разработчиков советников)
Правильный подход — не «исправлять ошибку после её появления», а ограничивать SL/TP минимальной дистанцией брокера ещё до отправки ордера, не допуская возникновения 10016 в принципе.
Проверка и ограничение SL/TP перед отправкой ордера
// Ограничиваем SL/TP дистанцией не менее уровня стопов и только затем отправляем ордер
// Возврат false = направление перепутано (ошибка в логике) — ордер отправлять не нужно
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);
// Запас = уровень стопов + спред (защита от брокеров с динамической оценкой при 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 для BUY проверяются относительно Bid
if(sl > 0 && sl >= bid) return false; // направление перепутано
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 для SELL проверяются относительно Ask
if(sl > 0 && sl <= ask) return false; // направление перепутано
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;
}
Три ключевых момента:
- Динамически получайте
SYMBOL_TRADE_STOPS_LEVELиSYMBOL_POINTпри каждом вызове — это делает код независимым от инструмента и брокера - Добавляйте запас на величину спреда — так запрос с большей вероятностью пройдёт даже у брокеров с динамической оценкой при нулевом уровне стопов
- В конце обязательно выравнивайте количество знаков через
NormalizeDouble(цена, _Digits)— лишние знаки после запятой тоже могут стать причиной отказа
Отдельная обработка retcode 10016
Если при отказе фиксировать в журнале, что именно было отправлено и каким было расстояние в тот момент, легко определить, какая из причин ①–⑥ сработала.
MqlTradeRequest req; MqlTradeResult res;
ZeroMemory(req); ZeroMemory(res);
// ... формируем req (sl/tp уже обработаны через 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());
}
Проверяйте уровень заморозки перед изменением трейлинга
// Перед изменением позиции проверяем, не попадает ли цена срабатывания в зону заморозки
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));
}
Для советников на MT4 те же значения можно получить через MarketInfo(Symbol(), MODE_STOPLEVEL) / MODE_FREEZELEVEL. Логика полностью аналогична.
Советники, которые распространяет FXEA365, изначально реализуют проверку и ограничение SL/TP перед отправкой ордера, а также динамическое получение параметров независимо от инструмента, поэтому смена брокера или торгового инструмента не приводит к остановке из-за ошибки Invalid stops.
Чек-лист по приоритетам
| Приоритет | Проверка | Решение |
|---|---|---|
| 🚨 В первую очередь | Расстояние между SL/TP и текущей ценой меньше уровня стопов? | Увеличить ширину SL/TP / реализовать ограничение (клампинг) |
| 🚨 В первую очередь | Не перепутано ли направление SL/TP при BUY/SELL? | Вывести реальные значения в журнал и проверить визуально |
| ⚠️ Далее | Отказ возникает только при изменении → попадание в зону заморозки? | Пропускать изменение непосредственно перед срабатыванием |
| ⚠️ Далее | Не передаётся ли в sl/tp расстояние (пункты) вместо цены? | Преобразовать в абсолютную цену price ± dist*_Point |
| ✅ Проверить | Требует ли счёт с рыночным исполнением установки SL/TP отдельным шагом? | Двухэтапная схема: отправка ордера → PositionModify |
| 🛠 Разработка | Получаются ли параметры инструмента динамически? | Реализовать ClampStops, приведённый выше |
Заключение
Invalid stops— это 10016 (TRADE_RETCODE_INVALID_STOPS) в MT5 и 130 (ERR_INVALID_STOPS) в MT4. Номера разные, но смысл и способ устранения общие.- В большинстве случаев причина не в ошибке расчёта, а в одном из шести факторов: расстояние меньше уровня стопов, перепутанное направление, уровень заморозки, путаница цены и пунктов, необходимость установки SL/TP отдельным шагом, особенности конкретного инструмента.
- Разработчики могут обеспечить постоянную защиту, если перед отправкой ордера считывать
SYMBOL_TRADE_STOPS_LEVEL, ограничивать значения и применятьNormalizeDouble. Даже брокеров с нулевым уровнем стопов можно подстраховать запасом на величину спреда.
Полный список кодов ошибок смотрите в сводном руководстве по устранению ошибок MQL5/MT5. Ещё одна распространённая ошибка при отказе в исполнении ордера — «недостаточно средств» — разобрана в статье про ERR_NO_MONEY (134/10019).
Частые вопросы
В: Расчёт SL/TP должен быть верным, но всё равно появляется Invalid stops. Почему?
Скорее всего, отказ связан не с корректностью значения, а с расстоянием от текущей цены. SL/TP на расстоянии меньше минимальной дистанции брокера (уровня стопов) отклоняются автоматически, даже если значение рассчитано верно. Проверьте минимальную дистанцию в окне спецификации или через SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL).
В: В чём разница между 10016 и 130?
10016 (TRADE_RETCODE_INVALID_STOPS) — это код результата OrderSend() / PositionModify() в MT5, а 130 (ERR_INVALID_STOPS) — значение, которое возвращает GetLastError() в MT4. Разница только в платформе: смысл (недопустимое положение SL/TP) и способ устранения одинаковы.
В: Обычно ошибки нет, но Invalid stops появляется только во время выхода новостей.
Причина — расширение спреда. Поскольку SL/TP для BUY проверяются относительно Bid, а для SELL — относительно Ask, именно в момент расширения спреда расстояния может не хватить. Даже у брокеров с уровнем стопов «0» встречается динамический отказ именно в моменты резких движений. Добавьте к расстоянию SL/TP запас на величину спреда (см. код в статье).
В: Ордер без SL/TP проходит, а с указанными SL/TP отклоняется.
Возможно, это счёт с рыночным исполнением (Market Execution), и сервер не принимает SL/TP непосредственно в запросе нового ордера. Перейдите на двухэтапную схему: отправляйте ордер без SL/TP, а после исполнения устанавливайте их через PositionModify(). Обязательно предусмотрите повтор попытки при неудачной установке SL и защитное немедленное закрытие позиции при повторяющихся неудачах.
В: Советник нормально работает на EURUSD, но на золоте постоянно выдаёт Invalid stops.
У золота и фондовых индексов уровень стопов обычно выше, чем у основных валютных пар, а также отличается количество знаков после запятой. Увеличьте ширину SL/трейлинга в соответствии со спецификацией инструмента либо перейдите на ширину, привязанную к волатильности (например, на основе ATR).
По теме
📧 Оповещения до повышения цен + бесплатный 5-дневный email-курс
Все советники продаются по стартовой цене, которая растёт ступенями по мере продаж. Получайте уведомление перед каждым повышением, плюс ежедневное письмо об алготрейдинге, чтении бэктестов и выборе брокера.
* Конфиденциальность строго защищена. Отписаться можно в любое время.