スポンサーリンク
MQL4 OrderSend, OrderModify and OrderClose Functions with Retry Handling

In MT4 EA development, trade execution usually depends on three core MQL4 functions: OrderSend(), OrderModify(), and OrderClose().
They are used to open positions, update stop-loss or take-profit levels, and close existing trades.
Calling these functions directly from many places in an EA can make trade logic difficult to maintain and debug. A reusable wrapper function lets you keep price handling, spread checks, retries, Magic Number filtering, and error logging in one place.
Quick answer:
Build small wrapper functions around
Build small wrapper functions around
OrderSend(), OrderModify(), and OrderClose().
Refresh prices before trading, use the correct Bid/Ask price, normalize price values, identify orders by symbol and Magic Number, and log GetLastError() whenever MT4 rejects an operation.What OrderSend, OrderModify and OrderClose Do
| Function | Main Role | Typical Use in an EA |
|---|---|---|
OrderSend() | Places a new market or pending order. | Open a Buy or Sell position when an entry condition becomes true. |
OrderModify() | Changes stop loss, take profit, pending order price, or expiration. | Set or update SL/TP after entry or move a stop with trailing logic. |
OrderClose() | Closes an existing market order. | Exit a Buy or Sell position when a close condition becomes true. |
Why Use Wrapper Functions in an MT4 EA?
Calling OrderSend() directly can be enough for a very small EA.
As the strategy becomes more complex, however, the same checks often appear in multiple entry and exit blocks.
A wrapper function allows those checks to be handled in one place.
- Use
Askfor Buy entries andBidfor Sell entries. - Call
RefreshRates()before reading the latest market price. - Check the spread before opening a new trade when the EA uses a spread limit.
- Normalize prices with
NormalizeDouble(price, Digits). - Retry temporary execution failures.
- Print
GetLastError()when an operation fails. - Filter existing positions by symbol and Magic Number.
Important:
Retry handling does not fix an invalid trade request. If the lot size is invalid, stop levels are too close, the price is incorrect, or free margin is insufficient, the underlying condition must be corrected.
Retry handling does not fix an invalid trade request. If the lot size is invalid, stop levels are too close, the price is incorrect, or free margin is insufficient, the underlying condition must be corrected.
Full MQL4 Sample Code
The following sample provides three reusable wrapper functions:
funcOrder_Send()— sends a Buy or Sell market order.funcOrder_Modify()— modifies the stop loss and take profit of an existing market order.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 market order |
//| |
//| ticket : target order ticket |
//| sl : new stop loss price, or 0 if not used |
//| tp : new take profit price, or 0 if not used |
//| maxRetries : retry count |
//| |
//| return true if the order is modified successfully |
//+------------------------------------------------------------------+
bool funcOrder_Modify(int ticket, double sl, double tp, int maxRetries)
{
for(int retry = 0; retry < maxRetries; retry++)
{
RefreshRates();
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;
}
if(OrderType() != OP_BUY && OrderType() != OP_SELL)
{
Print("funcOrder_Modify: market order required. Ticket=", ticket);
return false;
}
double normalizedSL =
(sl > 0) ? NormalizeDouble(sl, Digits) : 0;
double normalizedTP =
(tp > 0) ? NormalizeDouble(tp, Digits) : 0;
ResetLastError();
bool result = OrderModify(ticket,
OrderOpenPrice(),
normalizedSL,
normalizedTP,
0,
clrNONE);
if(result)
{
Print("OrderModify succeeded. Ticket=", ticket,
" SL=", normalizedSL,
" TP=", normalizedTP);
return true;
}
int err = GetLastError();
Print("OrderModify failed. Ticket=", ticket,
" Error=", err,
" Retry=", retry + 1, " / ", maxRetries);
Sleep(2000);
}
return false;
}
//+------------------------------------------------------------------+
//| Close market orders by symbol, type and Magic Number |
//| |
//| orderType : -1 = all, OP_BUY = buy only, OP_SELL = sell only|
//| magic : -1 = all Magic Numbers, otherwise exact match |
//| slippagePoints : slippage in points |
//| maxRetries : retry count |
//| |
//| return true if every selected order is closed successfully |
//+------------------------------------------------------------------+
bool funcOrder_CloseAll(int orderType, int magic,
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 closePrice = 0;
if(type == OP_BUY)
closePrice = NormalizeDouble(Bid, Digits);
else
closePrice = NormalizeDouble(Ask, Digits);
ResetLastError();
closed = OrderClose(ticket,
lots,
closePrice,
slippagePoints,
clrNONE);
if(closed)
{
Print("OrderClose succeeded. Ticket=", ticket,
" Lots=", lots,
" ClosePrice=", closePrice);
break;
}
int err = GetLastError();
Print("OrderClose failed. Ticket=", ticket,
" Error=", err,
" Retry=", retry + 1, " / ", maxRetries);
Sleep(2000);
}
if(!closed)
allSuccess = false;
}
return allSuccess;
} Why the close function does not use a spread filter:
A spread limit is useful for controlling new entries, but applying the same restriction to emergency or strategy exits can prevent a position from closing when spreads widen. If your strategy intentionally uses an exit spread filter, add it as a separate rule and define that behavior clearly.
A spread limit is useful for controlling new entries, but applying the same restriction to emergency or strategy exits can prevent a position from closing when spreads widen. If your strategy intentionally uses an exit spread filter, add it as a separate rule and define that behavior clearly.
How to Use funcOrder_Send()
Use funcOrder_Send() when an EA entry condition becomes true.
The function refreshes the current price, checks the spread, chooses the correct entry price, and logs the MT4 error code if the request fails.
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| Argument | Meaning |
|---|---|
orderType | OP_BUY or OP_SELL. |
sl / tp | Stop loss and take profit prices. Use 0 when they are not required. |
lots | Lot size. An invalid value can produce Error 131. |
magic | Magic Number used to identify the EA’s orders. |
maxSpreadPoints | Maximum entry spread in points. Use 0 to disable this check. |
slippagePoints | Allowed slippage in points. |
maxRetries | Maximum number of execution attempts. |
How to Use funcOrder_Modify()
Use funcOrder_Modify() when you want to update the stop loss or take profit of an existing market position.
The following example modifies Buy positions only. For Sell positions, the SL/TP direction must be reversed: a Sell stop loss is normally above the entry price, while a Sell take profit is normally below it.
for(int i = 0; i < OrdersTotal(); i++)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
if(OrderSymbol() != Symbol())
continue;
if(OrderMagicNumber() != 777)
continue;
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, 10);
} If OrderModify fails:
Check the stop-loss and take-profit distance from the current market price. Broker stop-level and freeze-level restrictions can prevent an otherwise valid ticket from being modified.
Check the stop-loss and take-profit distance from the current market price. Broker stop-level and freeze-level restrictions can prevent an otherwise valid ticket from being modified.
How to Use funcOrder_CloseAll()
Use funcOrder_CloseAll() when you want to close multiple market positions that match the current symbol, order type, and Magic Number.
The loop runs from the last order toward the first. This avoids the common problem of order indexes changing while positions are being closed.
// Close all market orders on the current symbol
// with Magic Number 777.
bool ok =
funcOrder_CloseAll(-1, 777, 20, 10);
// Close only Buy orders on the current symbol
// with Magic Number 777.
bool buyClosed =
funcOrder_CloseAll(OP_BUY, 777, 20, 10);| Filter | How It Works |
|---|---|
orderType | Use -1 for all market orders, OP_BUY for Buy only, or OP_SELL for Sell only. |
magic | Use -1 for all Magic Numbers or specify one Magic Number. |
Symbol() | The sample closes only orders belonging to the current chart symbol. |
Important MQL4 Order Handling Rules
Use the Correct Bid and Ask Price
For market orders, entry and closing prices depend on the order direction.
| Action | Price |
|---|---|
| Open Buy | Ask |
| Open Sell | Bid |
| Close Buy | Bid |
| Close Sell | Ask |
RefreshRates Before Using Bid or Ask
Price values can change between calculations and trade execution.
Calling RefreshRates() before reading Bid or Ask helps the EA use the latest price available to the terminal.
Normalize Price Values
Use:
NormalizeDouble(price, Digits)for prices passed to trade functions. This prevents avoidable precision problems when a calculated value contains more decimal places than the symbol uses.
Spread Values Are Returned in Points
MarketInfo(Symbol(), MODE_SPREAD) returns the spread in points.
For example, on a typical 5-digit EURUSD symbol:
10 points = 1 pip Make sure a parameter such as maxSpreadPoints is defined in points rather than assuming it represents pips.
Use Magic Numbers to Separate EA Orders
A Magic Number allows an EA to identify the orders it created. This becomes especially important when multiple EAs or manual positions are running in the same MT4 account.
Before modifying or closing an order, a typical EA should check both:
OrderSymbol()OrderMagicNumber()
Retry Handling Is Not a Substitute for Validation
Retry logic can help when a request fails because of a temporary execution condition. It cannot make an invalid trade request valid.
Examples include:
- An invalid entry price
- SL/TP levels that violate stop-distance rules
- An unsupported lot size
- Insufficient free margin
When a trade function fails, inspect the error code first and correct the actual cause.
Common OrderSend Errors
If OrderSend() still fails after the basic wrapper logic is in place, the error number usually identifies the next thing to check.
| Error | Meaning | Typical Cause | Detailed Guide |
|---|---|---|---|
| 129 | Invalid price | Wrong Bid/Ask, stale price, or invalid price precision. | Error 129 Guide |
| 130 | Invalid stops | Stop loss, take profit, or pending price violates broker distance rules. | Error 130 Guide |
| 131 | Invalid trade volume | Lot size does not match minimum lot, maximum lot, or lot step. | Error 131 Guide |
| 134 | Not enough money | Free margin is insufficient for the requested trade volume. | Error 134 Guide |
Related MQL4 OrderSend Error Guides
FAQ About MQL4 Order Functions
Q. What does OrderSend() do in MQL4?
OrderSend() sends a new trade request from an MT4 EA. It can be used to open Buy and Sell market positions as well as pending orders.
Q. What is the difference between OrderModify() and OrderClose()?
OrderModify() changes parameters of an existing order, such as stop loss or take profit. OrderClose() closes an existing market position.
Q. Should I call RefreshRates() before OrderSend()?
Calling RefreshRates() before using Bid or Ask is a common way to refresh the terminal’s current price values before sending or closing an order.
Q. Why does OrderSend() return -1?
OrderSend() returns -1 when the trade request fails. Call GetLastError() immediately after the failure to identify the cause, such as an invalid price, invalid stops, invalid trade volume, or insufficient margin.
Q. Why use a Magic Number in an MT4 EA?
A Magic Number identifies orders created by a particular EA. Checking both the symbol and Magic Number helps prevent one EA from modifying or closing positions that belong to another strategy.
Q. Should an EA block closing trades when the spread is high?
Not necessarily. A spread filter is commonly useful for new entries, but preventing an exit only because the spread widened can increase risk. Exit spread restrictions should be added only when they are part of the strategy’s intended logic.
Summary
OrderSend(),OrderModify(), andOrderClose()are core MQL4 trade functions used in MT4 EAs.- Wrapper functions keep execution checks and error handling in one reusable place.
- Use the correct Bid/Ask price and call
RefreshRates()before reading the latest prices. - Normalize trade prices and distinguish points from pips.
- Use symbol and Magic Number filters before modifying or closing positions.
- A spread filter is most naturally applied to new entries; exit restrictions should match the actual strategy logic.
- Use
GetLastError()to identify the cause when a trade function fails. - Errors 129, 130, 131, and 134 each require a different fix.
