Let's talk about
OrderSend(). Every MQL4 developer knows this function. You feed it the parameters, it sends the order, and you get a ticket number. Or an error code. And then what? Most of us just print the error, maybe do a Sleep(1000) and retry. That's the easy path.I spent a year building EAs for a prop firm that had some... let's say exotic execution rules. Their spreads were variable, their margin requirements changed based on account equity, and their maximum lot size per trade was a moving target. My simple retry-logic EAs were getting slaughtered. That's when I realized:
OrderSend() is just the trigger. The real art of execution lies in pre-validation and understanding the environment you're sending orders into.And the secret weapon for that environment is
MarketInfo(). The official MQL4 documentation (docs.mql4.com) lists its modes, but it barely scratches the surface of how you can chain these calls together to build a predictive model of order execution. This isn't glamorous work, but it separates EAs that survive from EAs that blow up.Beyond MODE_SPREAD: The Undervalued MarketInfo Queries
We all know
MarketInfo(Symbol(), MODE_SPREAD). But let's dig into the dark corners.MODE_MINLOT and MODE_MAXLOT: Obvious, right? But what about MODE_LOTSTEP? I can't tell you how many EAs I've seen that try to send a lot size of 0.07 on a broker where the lot step is 0.1. OrderSend() fails with error 131 (Invalid volume). A simple pre-check would have caught this. But wait, there's more. What if your calculated lot size is 0.15 and the step is 0.1? You need to round down to 0.1, or round to the nearest step. This is basic, yet I see it overlooked constantly.MODE_STOPLEVEL: This is the broker-imposed minimum distance for Stop Loss and Take Profit, usually in points. If you try to set a SL of 10 points on a broker with a 20-point stop level, your order will be rejected with error 130 (Invalid stops). Knowing this value before you call OrderSend() is critical.MODE_FREEZMODE: This is a weird one. It returns the freeze level, which is the distance in points where pending orders cannot be modified or deleted. If you're trying to modify an order that's within the freeze level, you'll get an error. Knowing this helps you decide if a modification is even worth attempting.The Hidden Quirk: MODE_MARGINREQUIRED and the Equity Trap
Here's where it gets interesting. And where my proprietary firm experience comes in.
MarketInfo(Symbol(), MODE_MARGINREQUIRED) returns the margin required to open one lot of a position. The documentation is clear on that. The hidden piece, and one that bit me hard, is that this value can change based on your account's leverage, which is static, and the symbol's margin rate, which can be dynamic. Some brokers offer reduced margin rates for major pairs during certain hours, or increased rates before news events.The quirk is this:
MarketInfo(Symbol(), MODE_MARGINREQUIRED) gives you the margin required per lot. But what if your account equity is close to the margin call level? You can calculate the maximum lots you can open using:MaxLots = (AccountFreeMargin() - BufferMargin) / MarketInfo(Symbol(), MODE_MARGINREQUIRED)The "BufferMargin" is my own addition. It's a safety net. I always add a buffer of 20-30% of the calculated margin to account for price slippage or sudden spread widening that could trigger an automatic margin call right after the order is placed.
The
AccountFreeMargin() check is a standard practice, but I take it further. Before sending the order, I actually calculate the new free margin after the order would be opened, and check if it's above a certain threshold. This prevents the nasty situation where your EA opens a trade, and the next tick triggers a margin stop-out because the free margin dipped just below the broker's threshold.My "Order Pre-Flight Checklist" Function
I built a function called
TradePreFlightCheck() that encapsulates all these checks. It's a bit long, but it's saved me from countless execution errors.``
mql4
//+------------------------------------------------------------------+
//| Trade Pre-Flight Check |
//+------------------------------------------------------------------+
bool TradePreFlightCheck(string symbol, double lotSize, double slPoints, double tpPoints, double &outLots)
{
// Reset output lot size
outLots = lotSize;
// 1. Check if trading is allowed
if(!IsTradeAllowed())
{
Print("Pre-Flight Error: Trade context is busy or not allowed.");
return false;
}
// 2. Validate Lot Size
double minLot = MarketInfo(symbol, MODE_MINLOT);
double maxLot = MarketInfo(symbol, MODE_MAXLOT);
double lotStep = MarketInfo(symbol, MODE_LOTSTEP);
if(lotStep == 0) {
Print("Pre-Flight Error: Lot step is zero. Cannot calculate valid lot size.");
return false;
}
// Adjust lot size to the nearest valid step, with floor to avoid going over max
double normalizedLots = MathFloor(outLots / lotStep) lotStep;// If the normalized lot is below min, set it to min.
if(normalizedLots < minLot) normalizedLots = minLot;
// If the normalized lot is above max, set it to max.
if(normalizedLots > maxLot) normalizedLots = maxLot;
// If after all this, the normalized lot is still not within range, it's a critical error.
if(normalizedLots < minLot || normalizedLots > maxLot)
{
Print("Pre-Flight Error: Lot size ", normalizedLots, " is out of bounds.");
return false;
}
outLots = normalizedLots; // Update the output reference
// 3. Validate Stop Loss and Take Profit levels
int stopLevel = (int)MarketInfo(symbol, MODE_STOPLEVEL);
int freezeLevel = (int)MarketInfo(symbol, MODE_FREEZELEVEL);
int minDist = MathMax(stopLevel, freezeLevel); // We must respect both
if(slPoints > 0 && slPoints < minDist)
{
Print("Pre-Flight Error: Stop Loss (", slPoints, " points) is less than minimum distance (", minDist, " points).");
return false;
}
if(tpPoints > 0 && tpPoints < minDist)
{
Print("Pre-Flight Error: Take Profit (", tpPoints, " points) is less than minimum distance (", minDist, " points).");
return false;
}
// 4. Check Free Margin
double marginPerLot = MarketInfo(symbol, MODE_MARGINREQUIRED);
if(marginPerLot == 0 || marginPerLot > AccountFreeMargin())
{
Print("Pre-Flight Error: Insufficient free margin. Required per lot: ", marginPerLot, ", Free margin: ", AccountFreeMargin());
return false;
}
// My buffer logic: Ensure we have at least 30% extra margin after the trade
double requiredMargin = outLots marginPerLot;
double freeMarginAfterTrade = AccountFreeMargin() - requiredMargin;
double bufferThreshold = AccountFreeMargin() 0.3; // 30% buffer
if(freeMarginAfterTrade < bufferThreshold)
{
Print("Pre-Flight Error: Margin buffer insufficient. Free margin after trade: ", freeMarginAfterTrade, ", required buffer: ", bufferThreshold);
return false;
}
// 5. Check for market open (optional but recommended)
if(!MarketInfo(symbol, MODE_TRADEALLOWED))
{
Print("Pre-Flight Error: Trading is not allowed for symbol ", symbol);
return false;
}
return true; // All checks passed
}
//+------------------------------------------------------------------+
`
A Different View on Error Handling
Here's my unique take: Most people treat OrderSend() errors as failures to be retried. I treat them as data points. When I get an error, I log everything: the error code, the precise values of all inputs, and the MarketInfo()` values at that moment. Over time, this creates a log of broker behavior.For example, I had an EA that kept getting error 128 (Trade timeout). The log showed it only happened during the London open, when volatility spiked. Instead of just retrying blindly, I added logic to detect this pattern and increase the slippage allowance during that period. It worked. Error 128 disappeared.
The official MetaQuotes article on "OrderSend Error Handling" suggests a simple retry loop. That's the mainstream approach. My approach treats error handling as a form of adaptive execution tuning, learning from the broker's environment in real-time.
Reference:
本文首发于FXEAR.com,原创内容,未经授权禁止转载。