Problem Description
Solution
To handle requotes effectively in MQL4, you need to wrap your OrderSend function in a loop. This loop must perform three specific actions:
- RefreshRates(): Update the local copy of prices (Ask/Bid) to the latest server values.
- Update Price: Use the new Ask or Bid price for the order.
- Sleep(): Wait a short moment before retrying to avoid spamming the server.
Here is a robust, reusable function OrderSendWithRetry that you can use to replace your standard OrderSend calls.
MQL4 Code Implementation
//+------------------------------------------------------------------+
//| RetryLogic.mq4 |
//| Copyright 2023, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Software Corp."
#property link "https://www.mql5.com"
#property version "1.00"
#property strict
//--- Input parameters
input int MagicNumber = 12345;
input double Lots = 0.1;
input int StopLoss = 50; // In points
input int TakeProfit = 100; // In points
input int MaxRetries = 5; // Number of times to retry on requote
input int RetryDelay = 3000; // Time to wait between retries in milliseconds
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// --- Example Trading Logic ---
// This is just a placeholder condition to demonstrate usage.
// Replace this with your actual strategy logic.
if(OrdersTotal() == 0)
{
// Example: Open a Buy Order
int ticket = OrderSendWithRetry(Symbol(), OP_BUY, Lots, 3, StopLoss, TakeProfit, "My Buy Order", MagicNumber, 0, clrGreen);
if(ticket > 0)
{
Print("Order opened successfully. Ticket: ", ticket);
}
else
{
Print("Failed to open order after retries.");
}
}
}
//+------------------------------------------------------------------+
//| Custom Function: OrderSend with Requote Handling |
//+------------------------------------------------------------------+
int OrderSendWithRetry(string symbol, int cmd, double volume, int slippage, int sl_points, int tp_points, string comment, int magic, datetime expiration, color arrow_color)
{
int ticket = -1;
double price, sl, tp;
// Loop through the maximum number of allowed retries
for(int i = 0; i <= MaxRetries; i++)
{
// 1. Refresh rates to get the absolute latest Bid/Ask
if(!RefreshRates())
{
Print("RefreshRates failed. Retrying...");
Sleep(RetryDelay);
continue;
}
// 2. Determine the correct price based on order type
if(cmd == OP_BUY)
{
price = Ask;
}
else if(cmd == OP_SELL)
{
price = Bid;
}
else
{
// For pending orders, price is usually passed explicitly.
// This example focuses on Market Orders (Instant Execution).
Print("This function is optimized for Market Orders (OP_BUY/OP_SELL)");
return(-1);
}
// 3. Calculate Stop Loss and Take Profit based on the NEW price
double point = MarketInfo(symbol, MODE_POINT);
if(sl_points > 0)
{
if(cmd == OP_BUY) sl = NormalizeDouble(price - sl_points * point, Digits);
else sl = NormalizeDouble(price + sl_points * point, Digits);
}
else sl = 0;
if(tp_points > 0)
{
if(cmd == OP_BUY) tp = NormalizeDouble(price + tp_points * point, Digits);
else tp = NormalizeDouble(price - tp_points * point, Digits);
}
else tp = 0;
// 4. Attempt to send the order
ticket = OrderSend(symbol, cmd, volume, price, slippage, sl, tp, comment, magic, expiration, arrow_color);
// 5. Check the result
if(ticket > 0)
{
// Order opened successfully, break the loop
return(ticket);
}
else
{
int err = GetLastError();
// Check specifically for Requote (Error 138) or Price Changed (Error 129)
if(err == ERR_REQUOTE || err == ERR_INVALID_PRICE || err == ERR_OFF_QUOTES)
{
Print("Error ", err, ": Requote/Price Changed. Attempt ", i+1, " of ", MaxRetries);
Sleep(RetryDelay); // Wait before trying again
}
else
{
// If it's a fatal error (e.g., Not Enough Money, Trade Disabled), stop trying immediately
Print("Fatal Error ", err, ". Stopping retries.");
return(-1);
}
}
}
return(-1); // Failed after max retries
}
//+------------------------------------------------------------------+
Key Components Explained
RefreshRates(): This is the most critical part. If you get a requote, it means the price you sent is old. Calling this function forces the EA to fetch the current market tick data. Without this, you would just keep sending the old, invalid price in every loop iteration.- Dynamic Price Calculation: Inside the loop, we recalculate
price = Ask(for Buy) orprice = Bid(for Sell) after refreshing rates. We also recalculate SL and TP relative to this new price to ensure valid distances. - Error Filtering: The code checks
GetLastError().- If the error is 138 (Requote) or 129 (Invalid Price), it waits and retries.
- If the error is something fatal like 134 (Not Enough Money), it breaks the loop immediately to prevent wasting time or freezing the EA.
Sleep(RetryDelay): This pauses execution (e.g., for 3 seconds) to allow the market volatility to settle slightly before sending the next request.
Q&A
Q: Does this work for Pending Orders (Buy Limit, Sell Stop)?
A: The logic provided above is specifically optimized for Market Orders (Instant Execution). Pending orders usually fail due to invalid stops (too close to price) rather than requotes. Requotes are a phenomenon of entering the market at the current price.
Q: What is the slippage parameter?
A: Slippage is the allowed deviation from the requested price. If you set slippage to 3 points, and the price moves by 2 points during transmission, the server will accept the trade without a requote. Increasing slippage is the first line of defense, while the retry loop is the second.
Q: Why do I need to recalculate Stop Loss and Take Profit inside the loop?
A: If the market price has moved (causing the requote), your original Stop Loss and Take Profit levels might now be too close to the new entry price, causing an ERR_INVALID_STOPS (130) error. Recalculating them ensures they maintain the correct distance from the new entry price.