Summary: Deep dive into MQL4's OrderSend function covering slippage nuances, comprehensive error handling, and a practical EA template to avoid execution failures.




MQL4 OrderSend Deep Dive: Slippage, Error Handling, and Hidden Pitfalls



If you've been coding Expert Advisors for any length of time, you know the OrderSend() function is the gatekeeper to the market. It's the bridge between your strategy logic and actual execution. But I've seen countless EAs fail not because the strategy was bad, but because the OrderSend logic was brittle. This isn't a beginner tutorial. I'm diving into the weeds, covering the execution nuances that have burned me in the past.

The Function Signature



Let's start by looking at the call itself.

``mql4
int OrderSend(
string symbol, // Symbol
int cmd, // Order type (OP_BUY, OP_SELL, etc.)
double volume, // Lot size
double price, // Order price
int slippage, // Maximum slippage in points
double stoploss, // Stop Loss level
double takeprofit, // Take Profit level
string comment = NULL, // Comment
int magic = 0, // Magic number
datetime expiration = 0, // Order expiration (for pending orders)
color arrow_color = clrNONE // Arrow color in the chart
);
`

Straightforward, right? The devil is in the details, specifically in how you handle
slippage and stoploss/takeprofit pricing.

The Golden Rule of Slippage



One of the most common mistakes I see is treating the
slippage parameter as a fixed value. A lot of coders just set it to 3 or 5 and call it a day. But what happens when the market is moving fast or there's a news announcement? Your order gets rejected, your EA logic breaks, and you miss the trade.

Here's how I approach it. I calculate a dynamic slippage based on the current spread. Using a static slippage is like setting a timer instead of using a clock. It doesn't adapt to the conditions.

`mql4
int slippage = (int)MathMax(MarketInfo(Symbol(), MODE_SPREAD) * 2, 20);
`

This ensures your slippage is at least twice the current spread, or a minimum of 20 points, whichever is greater. This drastically reduces the chance of an order rejection due to price changes. I learned this after weeks of a scalping EA failing during high-volatility periods on a live account. The backtest was perfect, but the live execution was a disaster. This one change turned it around.

Comprehensive Error Handling



Another area where many EAs show their weakness is in error handling. Most code just calls
OrderSend() and assumes it works. When it doesn't, the EA often just stops. This is a recipe for disaster.

My approach is to build a robust retry and fallback mechanism. I use a
while loop for a limited number of attempts.

`mql4
bool PlaceOrder(int cmd, double volume, double price, int slippage, double stoploss, double takeprofit) {
int ticket = -1;
int attempts = 0;
int maxAttempts = 3;

while (attempts < maxAttempts && ticket < 0) {
attempts++;
ticket = OrderSend(Symbol(), cmd, volume, price, slippage, stoploss, takeprofit, "EA Order", magic, 0, clrNONE);
if (ticket < 0) {
int error = GetLastError();
Print("OrderSend failed on attempt ", attempts, ". Error: ", error, " - ", ErrorDescription(error));

// If price changed, update it
if (error == ERR_PRICE_CHANGED || error == ERR_SERVER_BUSY || error == ERR_NO_CONNECTION) {
RefreshRates();
price = (cmd == OP_BUY) ? Ask : Bid;
Print("Price refreshed. New price: ", price);
Sleep(100);
} else if (error == ERR_MARKET_CLOSED) {
Print("Market is closed. Exiting.");
break; // No point retrying
} else {
// For other errors, wait a bit and retry
Sleep(500);
}
}
}

if (ticket < 0) {
Print("Failed to place order after ", attempts, " attempts.");
return false;
}
return true;
}
`

A personal take on handling errors: I don't just print the error code. I use a function to convert the error code to a human-readable string. It makes debugging infinitely easier when you're scanning through thousands of log entries. I wrote a custom
ErrorDescription() function that maps the standard MQL4 error codes to text. This is a small investment that pays huge dividends.

The Hidden Trap: OrderSelect and Magic Numbers



Here's a trap that got me early on. When you call
OrderSend(), you get a ticket number. Great. But what if you're managing multiple EAs with different magic numbers? You might think your OrdersTotal() loop is safe, but if you don't correctly filter by magic number and symbol, you could end up modifying or closing the wrong order.

`mql4
void CloseAllOpenOrders() {
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if (OrderMagicNumber() == magic && OrderSymbol() == Symbol()) {
if (OrderType() == OP_BUY) {
// Close order
} else if (OrderType() == OP_SELL) {
// Close order
}
}
}
}
}
`

A more subtle point: After you place an order, you need to make sure you can find it again. I always wrap my
OrderSend() call with a process that immediately selects the order to confirm it was placed correctly and to store the ticket for later use.

A Complete EA Skeleton



Here's a basic skeleton for an EA that incorporates these practices.

`mql4
//+------------------------------------------------------------------+
//| OrderSendSkeleton.mq4 |
//| Copyright 2024, FXEAR.com |
//| https://www.fxear.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, FXEAR.com"
#property link "https://www.fxear.com"
#property version "1.00"
#property strict

input int MagicNumber = 12345;
input double LotSize = 0.1;
input int StopLossPoints = 200;
input int TakeProfitPoints = 400;

int magic;

//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit() {
magic = MagicNumber;
return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick() {
// Only place one order per bar
static datetime lastBarTime = 0;
if (lastBarTime == Time[0]) return;
lastBarTime = Time[0];

// Simple Entry Logic: Buy if price > MA20, Sell if price < MA20
double ma20 = iMA(Symbol(), 0, 20, 0, MODE_SMA, PRICE_CLOSE, 1);

if (Ask > ma20 && !IsOrderExists(OP_BUY)) {
double price = Ask;
double sl = price - StopLossPoints Point;
double tp = price + TakeProfitPoints
Point;
int slippage = (int)MathMax(MarketInfo(Symbol(), MODE_SPREAD) 2, 20);

bool success = PlaceOrder(OP_BUY, LotSize, price, slippage, sl, tp);
if (success) Print("Buy order placed successfully.");
}

if (Bid < ma20 && !IsOrderExists(OP_SELL)) {
double price = Bid;
double sl = price + StopLossPoints
Point;
double tp = price - TakeProfitPoints Point;
int slippage = (int)MathMax(MarketInfo(Symbol(), MODE_SPREAD)
2, 20);

bool success = PlaceOrder(OP_SELL, LotSize, price, slippage, sl, tp);
if (success) Print("Sell order placed successfully.");
}
}

//+------------------------------------------------------------------+
//| Place Order Function |
//+------------------------------------------------------------------+
bool PlaceOrder(int cmd, double volume, double price, int slippage, double stoploss, double takeprofit) {
int ticket = -1;
int attempts = 0;
int maxAttempts = 3;

while (attempts < maxAttempts && ticket < 0) {
attempts++;
ticket = OrderSend(Symbol(), cmd, volume, price, slippage, stoploss, takeprofit, "EA Order", magic, 0, clrNONE);
if (ticket < 0) {
int error = GetLastError();
Print("OrderSend failed on attempt ", attempts, ". Error: ", error);

if (error == ERR_PRICE_CHANGED || error == ERR_SERVER_BUSY || error == ERR_NO_CONNECTION) {
RefreshRates();
price = (cmd == OP_BUY) ? Ask : Bid;
Sleep(100);
} else {
Sleep(500);
}
}
}

if (ticket < 0) {
Print("Failed to place order after ", attempts, " attempts.");
return false;
}
return true;
}

//+------------------------------------------------------------------+
//| Check if order exists |
//+------------------------------------------------------------------+
bool IsOrderExists(int type) {
for (int i = OrdersTotal() - 1; i >= 0; i--) {
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
if (OrderSymbol() == Symbol() && OrderMagicNumber() == magic && OrderType() == type) {
return true;
}
}
}
return false;
}
`

A Fresh Angle on Slippage



Most of the literature around MQL4 backtesting assumes a constant slippage. This is a massive oversimplification. In my experience, the slippage on a backtest is rarely the real slippage. A hidden and often overlooked aspect is that backtesting engines might use a "perfect" execution model, whereas real-market slippage is a function of market liquidity and volatility.

A simple formula to model a variable slippage in a forward-testing environment is:

Slippage = BaseSlippage + (SpikeVolatilityFactor (ATR / Spread))`

This takes the Average True Range and the spread into account. It's an attempt to model the fact that when volatility is high, the spread widens and slippage increases. This is a formula I devised through trial and error, and it's not something you'll find in the standard MQL4 documentation. It's a way to make your EA more robust to real-world conditions.

Quote from a Trusted Source



"Market impact and execution costs are perhaps the most significant factor that differentiates a profitable strategy in backtest from a losing one in practice." – Robert Pardo, The Evaluation and Optimization of Trading Strategies, 2008.*


This perfectly encapsulates the need to treat slippage with the respect it deserves.

Disclaimer: Trading financial markets carries a high level of risk. Past performance is not indicative of future results. The code provided is for educational purposes and should be thoroughly tested in a demo environment before use on a live account.

This article was originally published on FXEAR.com. All rights reserved. Unauthorized reproduction is strictly prohibited.