MQL4 OrderSend, OrderModify and OrderClose Functions with Retry Handling

MT4 and MQL4 technical notes eyecatch image with a degu mascot, code panel, and trading chart background
Advertisement

In MT4 EA development, trade execution usually depends on three core MQL4 functions: OrderSend(), OrderModify(), and OrderClose(). If these functions are called directly from many places in an EA, the code often becomes hard to debug when an order fails.

This article shows a reusable wrapper-style approach for market entries, stop-loss/take-profit modification, and closing existing positions. The examples are not meant to be a perfect trading system by themselves. They are a practical base for making order handling easier to read, retry, and debug.

Quick answer:
Build small wrapper functions around OrderSend(), OrderModify(), and OrderClose(). Inside those wrappers, call RefreshRates(), check the spread, use the correct Bid/Ask price, normalize prices, retry failed operations, and print GetLastError() when something fails.

What These MQL4 Order Functions Do

FunctionMain RoleTypical Use in an EA
OrderSend()Places a new market order or pending order.Open a Buy or Sell position when your entry condition becomes true.
OrderModify()Changes stop loss, take profit, pending order price, or expiration.Set or update SL/TP after entry, or move stop loss with trailing logic.
OrderClose()Closes an existing market order.Exit a Buy or Sell position when your close condition becomes true.

Why Use Wrapper Functions?

Calling OrderSend() directly can work for a very small EA, but repeated order code becomes fragile as the EA grows. A wrapper function lets you keep common checks in one place.

  • Use Ask for Buy entries and Bid for Sell entries.
  • Call RefreshRates() before reading the latest price.
  • Check the current spread before sending, modifying, or closing an order.
  • Normalize prices with NormalizeDouble(price, Digits).
  • Retry temporary failures instead of giving up immediately.
  • Print the error code from GetLastError() for debugging.
  • Filter positions by symbol and Magic Number before modifying or closing them.
Important:
These examples are for educational MQL4 code structure. Always test order logic in the MT4 Strategy Tester and on a demo account before using it in live trading.

Full MQL4 Sample Code

The following sample provides three wrapper functions:

  • funcOrder_Send(): sends a Buy or Sell market order.
  • funcOrder_Modify(): modifies stop loss and take profit for an existing ticket.
  • funcOrder_CloseAll(): closes selected market orders on the current symbol.
//+------------------------------------------------------------------+
//| Send a market order with spread check and retry handling           |
//|                                                                  |
//| orderType       : OP_BUY or OP_SELL                               |
//| sl              : stop loss price, or 0 if not used               |
//| tp              : take profit price, or 0 if not used             |
//| lots            : lot size                                        |
//| orderComment    : order comment                                   |
//| magic           : Magic Number                                    |
//| maxSpreadPoints : maximum allowed spread in points, 0 = no limit  |
//| slippagePoints  : slippage in points                              |
//| maxRetries      : retry count                                     |
//|                                                                  |
//| return true if the order is sent successfully                     |
//+------------------------------------------------------------------+
bool funcOrder_Send(int orderType, double sl, double tp, double lots,
                    string orderComment, int magic, int maxSpreadPoints,
                    int slippagePoints, int maxRetries)
{
   int ticket = -1;
   double price = 0;
   double spread = 0;
   color arrowColor = clrNONE;

   for(int retry = 0; retry < maxRetries; retry++)
   {
      RefreshRates();

      spread = MarketInfo(Symbol(), MODE_SPREAD);

      if(maxSpreadPoints > 0 && spread > maxSpreadPoints)
      {
         Print("Spread is too wide: ", spread,
               " points. Retry ", retry + 1, " / ", maxRetries);
         Sleep(2000);
         continue;
      }

      if(orderType == OP_BUY)
      {
         price = NormalizeDouble(Ask, Digits);
         arrowColor = clrBlue;
      }
      else if(orderType == OP_SELL)
      {
         price = NormalizeDouble(Bid, Digits);
         arrowColor = clrRed;
      }
      else
      {
         Print("funcOrder_Send: unsupported order type: ", orderType);
         return false;
      }

      double normalizedSL = (sl > 0) ? NormalizeDouble(sl, Digits) : 0;
      double normalizedTP = (tp > 0) ? NormalizeDouble(tp, Digits) : 0;

      ResetLastError();
      ticket = OrderSend(Symbol(), orderType, lots, price, slippagePoints,
                         normalizedSL, normalizedTP, orderComment, magic, 0, arrowColor);

      if(ticket > 0)
      {
         Print("OrderSend succeeded. Ticket=", ticket,
               " Price=", price, " Lots=", lots,
               " Spread=", spread, " points");
         return true;
      }

      int err = GetLastError();
      Print("OrderSend failed. Error=", err,
            " Retry=", retry + 1, " / ", maxRetries);
      Sleep(2000);
   }

   return false;
}

//+------------------------------------------------------------------+
//| Modify SL/TP for an existing order with retry handling             |
//|                                                                  |
//| ticket          : target order ticket                             |
//| sl              : new stop loss price, or 0 if not used           |
//| tp              : new take profit price, or 0 if not used         |
//| maxSpreadPoints : maximum allowed spread in points, 0 = no limit  |
//| maxRetries      : retry count                                     |
//|                                                                  |
//| return true if the order is modified successfully                 |
//+------------------------------------------------------------------+
bool funcOrder_Modify(int ticket, double sl, double tp,
                      int maxSpreadPoints, int maxRetries)
{
   if(!OrderSelect(ticket, SELECT_BY_TICKET))
   {
      Print("funcOrder_Modify: OrderSelect failed. Ticket=", ticket,
            " Error=", GetLastError());
      return false;
   }

   if(OrderSymbol() != Symbol())
   {
      Print("funcOrder_Modify: symbol mismatch. OrderSymbol=", OrderSymbol(),
            " ChartSymbol=", Symbol());
      return false;
   }

   double normalizedSL = (sl > 0) ? NormalizeDouble(sl, Digits) : 0;
   double normalizedTP = (tp > 0) ? NormalizeDouble(tp, Digits) : 0;

   for(int retry = 0; retry < maxRetries; retry++)
   {
      RefreshRates();

      double spread = MarketInfo(Symbol(), MODE_SPREAD);
      if(maxSpreadPoints > 0 && spread > maxSpreadPoints)
      {
         Print("Spread is too wide for OrderModify: ", spread,
               " points. Retry ", retry + 1, " / ", maxRetries);
         Sleep(2000);
         continue;
      }

      ResetLastError();
      bool result = OrderModify(ticket,
                                OrderOpenPrice(),
                                normalizedSL,
                                normalizedTP,
                                0,
                                clrNONE);

      if(result)
      {
         Print("OrderModify succeeded. Ticket=", ticket,
               " SL=", normalizedSL, " TP=", normalizedTP,
               " Spread=", spread, " points");
         return true;
      }

      int err = GetLastError();
      Print("OrderModify failed. Ticket=", ticket,
            " Error=", err,
            " Retry=", retry + 1, " / ", maxRetries);
      Sleep(2000);
   }

   return false;
}

//+------------------------------------------------------------------+
//| Close market orders with symbol, type, Magic Number and spread     |
//| filters                                                           |
//|                                                                  |
//| orderType       : -1 = all, OP_BUY = buy only, OP_SELL = sell only|
//| magic           : -1 = all Magic Numbers, otherwise exact match   |
//| maxSpreadPoints : maximum allowed spread in points, 0 = no limit  |
//| slippagePoints  : slippage in points                              |
//| maxRetries      : retry count                                     |
//|                                                                  |
//| return true if every selected order is closed successfully        |
//+------------------------------------------------------------------+
bool funcOrder_CloseAll(int orderType, int magic, int maxSpreadPoints,
                        int slippagePoints, int maxRetries)
{
   bool allSuccess = true;

   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
         continue;

      if(OrderSymbol() != Symbol())
         continue;

      if(magic != -1 && OrderMagicNumber() != magic)
         continue;

      int type = OrderType();
      if(type != OP_BUY && type != OP_SELL)
         continue;

      if(orderType == OP_BUY && type != OP_BUY)
         continue;

      if(orderType == OP_SELL && type != OP_SELL)
         continue;

      int ticket = OrderTicket();
      double lots = OrderLots();
      bool closed = false;

      for(int retry = 0; retry < maxRetries; retry++)
      {
         RefreshRates();

         double spread = MarketInfo(Symbol(), MODE_SPREAD);
         if(maxSpreadPoints > 0 && spread > maxSpreadPoints)
         {
            Print("Spread is too wide for OrderClose: ", spread,
                  " points. Ticket=", ticket,
                  " Retry=", retry + 1, " / ", maxRetries);
            Sleep(2000);
            continue;
         }

         double closePrice = 0;
         color arrowColor = clrNONE;

         if(type == OP_BUY)
         {
            closePrice = NormalizeDouble(Bid, Digits);
            arrowColor = clrBlue;
         }
         else if(type == OP_SELL)
         {
            closePrice = NormalizeDouble(Ask, Digits);
            arrowColor = clrRed;
         }

         ResetLastError();
         closed = OrderClose(ticket, lots, closePrice, slippagePoints, arrowColor);

         if(closed)
         {
            Print("OrderClose succeeded. Ticket=", ticket,
                  " Lots=", lots,
                  " ClosePrice=", closePrice,
                  " Spread=", spread, " points");
            break;
         }

         int err = GetLastError();
         Print("OrderClose failed. Ticket=", ticket,
               " Error=", err,
               " Retry=", retry + 1, " / ", maxRetries);
         Sleep(2000);
      }

      if(!closed)
         allSuccess = false;
   }

   return allSuccess;
}

How to Use funcOrder_Send()

Use funcOrder_Send() when your EA entry condition is true. The function checks the spread, chooses the correct entry price, retries when the order fails, and prints the error code if MT4 rejects the request.

bool ok = funcOrder_Send(OP_BUY,
                         0,        // stop loss, 0 = no SL
                         0,        // take profit, 0 = no TP
                         0.10,     // lots
                         "sample buy",
                         777,      // Magic Number
                         30,       // max spread in points
                         20,       // slippage in points
                         10);      // retries
ArgumentMeaning
orderTypeOP_BUY or OP_SELL.
sl / tpStop loss and take profit prices. Use 0 if not needed.
lotsLot size. If this is invalid, MT4 may return Error 131.
magicMagic Number used to identify this EA’s orders.
maxSpreadPointsMaximum allowed spread in points. Use 0 to skip the spread check.
slippagePointsAllowed slippage in points.
maxRetriesHow many times the function retries before returning false.

How to Use funcOrder_Modify()

Use funcOrder_Modify() when you want to update stop loss or take profit for an existing order. Before calling it, select the target order and calculate the new SL/TP levels according to your strategy rules.

The following example modifies Buy positions only. For Sell positions, the SL/TP direction must be reversed: a Sell stop loss is usually above the open price, and a Sell take profit is usually below the open price.

for(int i = 0; i < OrdersTotal(); i++)
{
   if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      continue;

   if(OrderSymbol() != Symbol())
      continue;

   if(OrderMagicNumber() != 777)
      continue;

   // This example is Buy-only.
   // For Sell positions, reverse the SL/TP direction.
   if(OrderType() != OP_BUY)
      continue;

   double newSL = NormalizeDouble(OrderOpenPrice() - 50 * Point, Digits);
   double newTP = NormalizeDouble(OrderOpenPrice() + 100 * Point, Digits);

   bool ok = funcOrder_Modify(OrderTicket(), newSL, newTP, 30, 10);
}

The function uses OrderSelect(ticket, SELECT_BY_TICKET) internally and also checks that the selected order belongs to the current chart symbol. This helps avoid accidentally modifying another symbol’s position.

Tip:
If OrderModify() fails even though the ticket is correct, check the broker’s stop distance rules. Invalid stop loss or take profit levels often lead to OrderSend Error 130 or an OrderModify failure with a similar stop-distance problem.

How to Use funcOrder_CloseAll()

Use funcOrder_CloseAll() when you want to close multiple positions that match the current symbol, order type, and Magic Number. The function loops from the newest order to the oldest order, which is safer when closing positions inside an order loop.

// Close all market orders on the current symbol with Magic Number 777.
bool ok = funcOrder_CloseAll(-1, 777, 30, 20, 10);

// Close only Buy orders on the current symbol with Magic Number 777.
bool buyClosed = funcOrder_CloseAll(OP_BUY, 777, 30, 20, 10);
FilterHow It Works
orderTypeUse -1 for all market orders, OP_BUY for Buy only, or OP_SELL for Sell only.
magicUse -1 for all Magic Numbers, or pass a specific Magic Number.
Symbol()The sample only closes orders on the current chart symbol.

Important Implementation Notes

Use the Correct Bid/Ask Price

For market orders, the entry and close price must match the order direction.

ActionCorrect Price
Open BuyAsk
Open SellBid
Close BuyBid
Close SellAsk

Spread Check Uses Points

MarketInfo(Symbol(), MODE_SPREAD) returns the spread in points. On a 5-digit EURUSD symbol, 10 points usually means 1 pip. Make sure your maxSpreadPoints value matches your broker’s digit format.

Retry Handling Is Not a Substitute for Validation

Retry logic can help with temporary price changes or short-lived trade context issues. It cannot fix invalid parameters. If the lot size is outside the broker’s allowed range, the stop loss is too close, or there is not enough margin, retrying the same request will usually fail again.

Common OrderSend Errors to Check Next

ErrorMeaningTypical CauseDetailed Guide
129Invalid priceWrong Bid/Ask, old price, or unnormalized price.OrderSend Error 129 in MT4/MQL4
130Invalid stopsStop loss or take profit is too close to the current price.OrderSend Error 130 in MT4/MQL4
131Invalid trade volumeLot size does not match min lot, max lot, or lot step.OrderSend Error 131 in MT4/MQL4
134Not enough moneyFree margin is not enough for the requested lot size.OrderSend Error 134 in MT4/MQL4

Summary

  • OrderSend(), OrderModify(), and OrderClose() are the core trade functions used in MT4 EAs.
  • Wrapper functions make order handling easier to reuse and debug.
  • Always call RefreshRates() before reading the latest Bid/Ask price.
  • Check spread, normalize prices, and print GetLastError() when an order fails.
  • Use symbol and Magic Number filters before modifying or closing existing orders.
タイトルとURLをコピーしました