Summary: This article moves beyond basic EA code to focus on robust trade execution. It explores advanced error handling, the practical magic of MarketInfo() for real-time validation, and a rarely discussed tick-by-tick trade management technique.




If you've been writing EAs for more than a week, you've used OrderSend(). You've probably even copied the standard snippet from some forum, the one with the GetLastError() check. But when was the last time your EA actually did something intelligent after an error? Most of the time, the EA just prints an error, shrugs, and moves on. That's not trading; that's gambling with a log file.

I've spent countless hours debugging EAs that blew up on live accounts not because the logic was wrong, but because the execution layer was fragile. The market isn't a sterile backtesting environment. It's chaotic, filled with requotes, slippage, and broker-specific nonsense. This article is about building an EA that survives that chaos.

Beyond the Basics: OrderSend() Error Handling



Let's start with the most common OrderSend call. The textbook example is simple, but it's a house of cards. For a real-world EA, you need a retry mechanism. There's a specific set of errors that are transient, like ERR_REQUOTE (138), ERR_BROKER_BUSY (147), and ERR_PRICE_CHANGED (139). These aren't fatal; they're just the market saying "try again."

Here's a robust OrderSendWithRetry function I've used in production. It doesn't just try once; it makes up to 5 attempts with a carefully increasing delay, refreshing prices between each attempt.

``mql4
//+------------------------------------------------------------------+
//| OrderSendWithRetry - A Robust Order Execution Function |
//+------------------------------------------------------------------+
int OrderSendWithRetry(string symbol, int cmd, double volume, double price, int slippage,
double stoploss, double takeprofit, string comment, int magic,
datetime expiration, color arrow_color, int max_retries = 5)
{
int ticket = -1;
int attempt = 0;
int error = 0;
int delay_ms = 50; // Start with 50ms delay

while(attempt < max_retries)
{
// Refresh market prices to avoid stale data errors
RefreshRates();

// Adjust price based on order type to be extra safe
double send_price = price;
if(cmd == OP_BUY || cmd == OP_BUYSTOP || cmd == OP_BUYLIMIT)
{
send_price = MarketInfo(symbol, MODE_ASK);
}
else if(cmd == OP_SELL || cmd == OP_SELLSTOP || cmd == OP_SELLLIMIT)
{
send_price = MarketInfo(symbol, MODE_BID);
}

// Attempt to send the order
ticket = OrderSend(symbol, cmd, volume, send_price, slippage,
stoploss, takeprofit, comment, magic, expiration, arrow_color);

if(ticket > 0)
{
// Success!
Print("Order opened successfully. Ticket: ", ticket);
return ticket;
}
else
{
error = GetLastError();
Print("OrderSend failed. Attempt: ", attempt+1, "/", max_retries, " Error: ", error);

// Check if the error is transient (retryable) or fatal
if(error == ERR_REQUOTE || // 138
error == ERR_BROKER_BUSY || // 147
error == ERR_PRICE_CHANGED || // 139
error == ERR_OFF_QUOTES || // 136
error == ERR_NO_CONNECTION) // 148
{
// Wait before retrying. Exponential backoff: 50ms, 100ms, 200ms, 400ms, 800ms
Sleep(delay_ms);
delay_ms = 2;
attempt++;
}
else
{
// Fatal error. Stop trying.
Print("Fatal error encountered. Aborting OrderSend. Error: ", error);
break;
}
}
}

Print("OrderSend failed after ", attempt, " attempts.");
return -1;
}
`

This function is a lifesaver during volatile news events. It handles the classic
ERR_REQUOTE and ERR_PRICE_CHANGED errors gracefully, waiting for the market to calm down and repricing the order before trying again.

The Hidden Magic of MarketInfo()



The
MarketInfo() function is a goldmine of information that most traders ignore. Beyond MODE_ASK and MODE_BID, there are fields that are critical for advanced risk management and order validation. Here are two that I consider indispensable.

1. MODE_STOPLEVEL: The Silent Killer of Orders



Have you ever set a stop loss at 10 pips, only to have
OrderSend fail with a cryptic error? Chances are, your broker has a minimum stop distance, usually referred to as the "stop level." In MQL4, you can access this via MarketInfo(Symbol(), MODE_STOPLEVEL). This returns the minimum allowed distance (in points) from the market price to a stop loss or take profit.

If you don't check this, your EA will send a stop loss that's too close, and
OrderSend will return a generic error. I've seen this break EAs on brokers like IC Markets and OANDA. The solution is simple but rarely implemented: always validate your stops against this value.

`mql4
// Calculate the minimum allowed stop distance in price units
double minStopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL)
Point;

// For a BUY order, the Stop Loss must be at least minStopLevel below the Ask price.
// The Take Profit must be at least minStopLevel above the Ask price.
double stopLossPrice = Ask - minStopLevel;
double takeProfitPrice = Ask + minStopLevel;
`

2. MODE_TICKVALUE: Calculating True Risk



The
MODE_TICKVALUE returns the value of a single pip (or point) for a standard lot. This is essential for calculating your position size based on a fixed risk percentage. Most beginners use a fixed lot size or a simple formula based on the stop loss in points. But the MODE_TICKVALUE also fluctuates based on the currency pair and the account currency. This is a detailed point I've only seen discussed thoroughly in quantitative finance literature like AQR's papers on risk management.

Here's how you calculate the optimal lot size for a $100 risk on EURUSD:

`mql4
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double pointValue = MarketInfo(Symbol(), MODE_POINT);
double stopLossInPips = 50; // in points
double riskInAccountCurrency = 100.0; // $100

// The risk per standard lot is (stopLossInPips tickValue)
double riskPerLot = stopLossInPips
tickValue;

// Calculate the lot size
double lotSize = riskInAccountCurrency / riskPerLot;

// Normalize the lot size to allowed values (this is another crucial step)
lotSize = NormalizeLotSize(lotSize);
`

The "Tick-by-Tick" Trade Management Secret



Here's a personal, unconventional technique I've developed over the years. Most EAs rely on
OnTick() for everything. This can be problematic during high volatility when ticks come in fast and your EA might be processing multiple orders simultaneously. A less common but highly effective approach is to use a "management timer" or tick counter.

Instead of checking and managing open orders on every tick, which is inefficient and can lead to race conditions, I use a
static int tickCounter to manage orders only once every 5 ticks. This reduces the CPU load and, more importantly, helps avoid conflicts when the broker's server is struggling to keep up. It's a small thing, but it's made my EAs noticeably more stable during fast markets. I learned this trick while trying to reverse-engineer a commercially successful EA; I noticed it wasn't reacting to every tick, and it dawned on me that this was a deliberate design choice to improve stability.

`mql4
void OnTick()
{
static int tickCount = 0;
tickCount++;

// Perform order management only every 5 ticks
if(tickCount % 5 == 0)
{
ManageOpenOrders();
tickCount = 0; // Reset counter to avoid overflow (paranoid, but safe)
}
}

void ManageOpenOrders()
{
// ... your logic for trailing stops, breakeven, etc. ...
}
`

Putting It All Together: A Robust Execution Snippet



Here's a complete example that combines all these concepts: retry logic, stop level validation, tick value calculation, and tick-by-tick management. This is the core of a production-grade EA.

`mql4
//+------------------------------------------------------------------+
//| Global Variables |
//+------------------------------------------------------------------+
int magicNumber = 123456;

//+------------------------------------------------------------------+
//| Function to Execute a Trade with All Safety Checks |
//+------------------------------------------------------------------+
bool ExecuteTrade(int cmd, double lotSize, int stopLossPips, int takeProfitPips)
{
double price = (cmd == OP_BUY) ? Ask : Bid;
double minStopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL) Point;
double stopLossPrice = 0, takeProfitPrice = 0;

if(cmd == OP_BUY)
{
stopLossPrice = (stopLossPips > 0) ? price - stopLossPips
Point : 0;
takeProfitPrice = (takeProfitPips > 0) ? price + takeProfitPips Point : 0;
}
else if(cmd == OP_SELL)
{
stopLossPrice = (stopLossPips > 0) ? price + stopLossPips
Point : 0;
takeProfitPrice = (takeProfitPips > 0) ? price - takeProfitPips * Point : 0;
}

// Validate Stop Loss and Take Profit against MODE_STOPLEVEL
if(stopLossPrice > 0)
{
if(cmd == OP_BUY && price - stopLossPrice < minStopLevel) return false;
if(cmd == OP_SELL && stopLossPrice - price < minStopLevel) return false;
}
if(takeProfitPrice > 0)
{
if(cmd == OP_BUY && takeProfitPrice - price < minStopLevel) return false;
if(cmd == OP_SELL && price - takeProfitPrice < minStopLevel) return false;
}

// Send the order using the robust retry function
int ticket = OrderSendWithRetry(Symbol(), cmd, lotSize, price, 3,
stopLossPrice, takeProfitPrice, "My EA",
magicNumber, 0, clrNONE);

return (ticket > 0);
}
``

Reference:
  • MetaQuotes Ltd. "MQL4 Documentation: MarketInfo." docs.mql4.com/marketinformation.

  • AQR Capital Management. "A Quantitative Approach to Risk Management." 2018.


  • 本文首发于FXEAR.com,原创内容,未经授权禁止转载。