Fix MQL5 error 4756 (invalid trade request) when OrderSend fails

Published 2026-07-11 · Ranar Algo

Error 4756 is not a broker problem, and it is almost never "market conditions". It means the trade request the terminal received was malformed, so it was thrown out before anyone on the server side looked at it. Annoying, but useful: malformed requests are nearly always your code, and your code is the part you can fix in five minutes.

What 4756 actually means

You call OrderSend(), it returns false, and GetLastError() gives you 4756 — invalid trade request. Look at result.retcode as well. It will often be 0, meaning the request never left the terminal, or 10013 (TRADE_RETCODE_INVALID).

That distinction is the whole diagnosis. A server-side rejection — requote, no money, market closed — comes back as a 100xx retcode, which proves the request arrived and was well-formed. A 4756 with retcode 0 means the client refused to send it. Nothing about your strategy is being tested at that point.

The causes that cover almost every case

Build the request from scratch, every time

The fix is boring and reliable: zero the struct, fill every field you need, and validate the numbers before OrderSend sees them.

//+------------------------------------------------------------------+
//| Decimal places in the symbol's lot step                          |
//+------------------------------------------------------------------+
int LotDigits(const double step)
  {
   int    digits = 0;
   double s      = step;
   while(digits < 8 && MathAbs(s - MathRound(s)) > 1e-9)
     {
      s *= 10.0;
      digits++;
     }
   return(digits);
  }

//+------------------------------------------------------------------+
//| Filling mode the symbol advertises for market orders             |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING MarketFillingMode(const string symbol)
  {
   long modes = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
   if((modes & SYMBOL_FILLING_FOK) != 0)
      return(ORDER_FILLING_FOK);
   if((modes & SYMBOL_FILLING_IOC) != 0)
      return(ORDER_FILLING_IOC);
   return(ORDER_FILLING_RETURN);   // pending orders only
  }

//+------------------------------------------------------------------+
//| Market order with client-side validation                         |
//+------------------------------------------------------------------+
bool SendMarketOrder(const string          symbol,
                     const ENUM_ORDER_TYPE type,
                     const double          volume,
                     const double          sl,
                     const double          tp,
                     const ulong           magic,
                     const string          comment)
  {
   if(type != ORDER_TYPE_BUY && type != ORDER_TYPE_SELL)
     {
      Print("SendMarketOrder: unsupported type");
      return(false);
     }
   if(!SymbolSelect(symbol, true))
     {
      Print("SendMarketOrder: unknown symbol ", symbol);
      return(false);
     }

   MqlTick tick;
   if(!SymbolInfoTick(symbol, tick) || tick.ask <= 0.0 || tick.bid <= 0.0)
     {
      Print("SendMarketOrder: no valid tick for ", symbol);
      return(false);
     }

   const double step   = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   const double minvol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   const double maxvol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   if(step <= 0.0 || minvol <= 0.0)
     {
      Print("SendMarketOrder: bad volume limits for ", symbol);
      return(false);
     }

   double vol = MathRound(volume / step) * step;
   vol = MathMax(minvol, MathMin(maxvol, vol));
   vol = NormalizeDouble(vol, LotDigits(step));

   const int    digits    = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
   const double point     = SymbolInfoDouble(symbol, SYMBOL_POINT);
   const long   stopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
   const double minDist   = (double)stopLevel * point;

   const double price = NormalizeDouble((type == ORDER_TYPE_BUY) ? tick.ask : tick.bid,
                                        digits);

   double stopLoss   = (sl > 0.0) ? NormalizeDouble(sl, digits) : 0.0;
   double takeProfit = (tp > 0.0) ? NormalizeDouble(tp, digits) : 0.0;

   if(stopLoss > 0.0)
     {
      if(type == ORDER_TYPE_BUY  && stopLoss   >= price - minDist) { Print("SL too close"); return(false); }
      if(type == ORDER_TYPE_SELL && stopLoss   <= price + minDist) { Print("SL too close"); return(false); }
     }
   if(takeProfit > 0.0)
     {
      if(type == ORDER_TYPE_BUY  && takeProfit <= price + minDist) { Print("TP too close"); return(false); }
      if(type == ORDER_TYPE_SELL && takeProfit >= price - minDist) { Print("TP too close"); return(false); }
     }

   MqlTradeRequest request;
   MqlTradeResult  result;
   ZeroMemory(request);
   ZeroMemory(result);

   request.action       = TRADE_ACTION_DEAL;
   request.symbol       = symbol;
   request.volume       = vol;
   request.type         = type;
   request.price        = price;
   request.sl           = stopLoss;
   request.tp           = takeProfit;
   request.deviation    = 20;
   request.magic        = magic;
   request.comment      = comment;
   request.type_filling = MarketFillingMode(symbol);

   ResetLastError();
   if(!OrderSend(request, result))
     {
      PrintFormat("%s: OrderSend failed, err=%d retcode=%u comment=%s",
                  __FUNCTION__, GetLastError(), result.retcode, result.comment);
      return(false);
     }

   if(result.retcode != TRADE_RETCODE_DONE &&
      result.retcode != TRADE_RETCODE_PLACED)
     {
      PrintFormat("%s: rejected, retcode=%u comment=%s",
                  __FUNCTION__, result.retcode, result.comment);
      return(false);
     }
   return(true);
  }

Normalise the numbers, do not trust your inputs

Two lines in that function do most of the work. MathRound(volume / step) * step snaps the volume to a legal lot, and LotDigits() derives the decimal count from the step itself rather than everyone's favourite hard-coded 2. A symbol with a 0.001 step and a hard-coded two decimals gives you a volume the terminal rejects.

The same applies to prices. SYMBOL_DIGITS on a five-digit FX pair is not 2, and a price with the wrong number of decimals is a malformed request.

Filling mode is usually 10030, not 4756

Worth being precise here, because the internet is not. An unsupported filling mode normally comes back as retcode 10030, TRADE_RETCODE_INVALID_FILL, not 4756. But a garbage value in type_filling can still make the terminal reject the request outright. Ask the symbol via SYMBOL_FILLING_MODE — and remember ORDER_FILLING_RETURN is for pending orders, not TRADE_ACTION_DEAL.

Log the retcode, and do not retry blindly

GetLastError() tells you the terminal gave up; result.retcode and result.comment tell you which side rejected the request and often why. Log both, and call ResetLastError() before OrderSend so you are not reading a stale code from somewhere else in your loop.

Retrying on 4756 is pointless. The request is malformed, and it will stay malformed on attempt two, three and thirty. Fix the struct instead. For an example of what the output looks like once the plumbing is right, [see a real backtest](https://mql.ranartech.com/r/SUd0aZyv5qUw) — a GBPUSD H1 EA, +$656, 37% win rate across 107 trades.

If you take one thing from this: ZeroMemory() the request, set every field explicitly, validate volume and price against the symbol's own specifications, and log the retcode rather than the error alone. That covers 4756.

Build & backtest your own EA free →

Ranar Algo turns a plain-English strategy into a compiled MQL5 Expert Advisor with a real MetaTrader 5 backtest. You keep your IP. Past performance does not guarantee future results.