Summary: Why your MQL4 EA works in backtest but fails live: the floating STOPLEVEL problem and how to build a robust order execution wrapper.




The Bug That Only Shows Up on Live Accounts



I've lost count of how many times I've seen this. An EA backtests beautifully. The logic is clean, the entry conditions are sharp, the exit rules make sense. You deploy it on a demo account, works fine. Then you go live—and OrderSend starts throwing error 130 like it's going out of style.

The first instinct is always the same: "The broker is screwing me." That's rarely the actual problem.

The real issue is usually buried in one line of code that most MQL4 developers copy from a tutorial and never think about again. It's this:

``mql4
double stopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL);
`

That looks harmless. The MQL4 documentation says
MODE_STOPLEVEL returns "Stop level in points" . The doc also contains a sentence that most people skim right past:

"A zero value of MODE_STOPLEVEL means either absence of any restrictions on the minimal distance for Stop Loss/Take Profit or the fact that a trade server utilizes some external mechanisms for dynamic level control, which cannot be translated in the client terminal. In the second case, GetLastError() can return error 130, because MODE_STOPLEVEL is actually 'floating' here."


Read that again. The documentation is explicitly telling you that on some servers,
MODE_STOPLEVEL returns zero, but the server still enforces a minimum stop distance through its own mechanisms. Your EA sees "no restriction," calculates a stop loss 5 points from entry, sends the order, and the server rejects it.

This isn't a theoretical edge case. I've encountered it on at least three different ECN brokers over the past two years. The symptom is always the same:
OrderSend returns -1, GetLastError() returns 130, and the EA sits there retrying the same invalid stop level on every tick.

The Fix Most People Get Wrong



The common "fix" is to hardcode a minimum distance:

`mql4
double stopLevel = MathMax(MarketInfo(Symbol(), MODE_STOPLEVEL), 10 Point);
`

This works if you happen to pick a number that's larger than whatever the server is secretly enforcing. It's a band-aid, not a solution. Pick 10 points, and the broker switches to 15. Pick 15, and you get requotes because your stop is still too tight for volatile conditions.

What I do instead is a two-stage approach: first, use
MODE_STOPLEVEL when it's available; second, when it returns zero, fall back to a dynamic buffer based on the spread.

`mql4
//+------------------------------------------------------------------+
//| Returns the effective minimum stop distance in price terms |
//| Handles the "floating STOPLEVEL" problem on certain brokers |
//+------------------------------------------------------------------+
double GetEffectiveStopLevel()
{
double stopLevel = MarketInfo(Symbol(), MODE_STOPLEVEL);

// If STOPLEVEL is zero, the server may still enforce a hidden minimum.
// Fall back to a spread-based buffer: 3x current spread, minimum 20 points.
if(stopLevel <= 0)
{
double spread = MarketInfo(Symbol(), MODE_SPREAD);
stopLevel = MathMax(spread
3.0, 20.0);
}

// Add a 20% safety margin on top of whatever we calculated.
return stopLevel * 1.2;
}
`

The logic here: when
MODE_STOPLEVEL is useless, the spread is your best proxy for what the broker considers "too close." A three-spread minimum plus a safety margin has worked across every broker I've tested it on. It's not elegant, but it's robust.

Why OrderSend Fails Before It Even Reaches the Server



Here's something the documentation mentions but doesn't emphasize enough:
OrderSend validates your stop loss and take profit levels before sending the request. If the levels are invalid, the function returns -1 immediately, without ever hitting the broker's servers .

This matters for debugging. If you're logging errors and seeing 130 repeatedly, the problem might not be the server at all. It might be that your EA calculated a stop level that MQL4 itself considers invalid based on the
MODE_STOPLEVEL value it does have.

The doc is explicit about this:

"StopLoss and TakeProfit levels cannot be too close to the market. The minimal distance of stop levels in points can be obtained using the MarketInfo() function with MODE_STOPLEVEL parameter. In the case of erroneous or unnormalized stop levels, the error 130 (ERR_INVALID_STOPS) will be generated."


So the error can come from two places: the client terminal's validation, or the server's rejection. The fix is the same, but knowing which one is rejecting you helps you calibrate your safety margin.

The Order Execution Wrapper I Actually Use



Instead of calling
OrderSend directly, I wrap it in a function that handles normalization, stop level validation, and error retry logic. Here's the core of it:

`mql4
//+------------------------------------------------------------------+
//| Robust order opening wrapper for MQL4 |
//| Returns ticket number on success, -1 on failure |
//+------------------------------------------------------------------+
int SafeOrderSend(int cmd, double lots, double price, int slippage,
double slPrice, double tpPrice, string comment, int magic)
{
// Normalize prices to the correct number of digits
int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
price = NormalizeDouble(price, digits);
slPrice = NormalizeDouble(slPrice, digits);
tpPrice = NormalizeDouble(tpPrice, digits);

// Validate stop levels against the effective minimum
double minStop = GetEffectiveStopLevel();
double stopDist = minStop Point;

if(cmd == OP_BUY)
{
if(slPrice > 0 && (price - slPrice) < stopDist)
slPrice = NormalizeDouble(price - stopDist, digits);
if(tpPrice > 0 && (tpPrice - price) < stopDist)
tpPrice = NormalizeDouble(price + stopDist, digits);
}
else if(cmd == OP_SELL)
{
if(slPrice > 0 && (slPrice - price) < stopDist)
slPrice = NormalizeDouble(price + stopDist, digits);
if(tpPrice > 0 && (price - tpPrice) < stopDist)
tpPrice = NormalizeDouble(price - stopDist, digits);
}

// Attempt the order with a single retry on requote
int ticket = OrderSend(Symbol(), cmd, lots, price, slippage,
slPrice, tpPrice, comment, magic, 0, clrNONE);

if(ticket < 0)
{
int err = GetLastError();
Print("OrderSend failed. Error: ", err,
" | SL: ", slPrice, " TP: ", tpPrice,
" | minStop: ", minStop);
}

return ticket;
}
`

Notice that I adjust the SL/TP prices upward or downward to meet the minimum distance, rather than rejecting the order. This is a design choice. Some developers prefer to skip the trade entirely if the stop can't be placed where they want it. I prefer to place the trade with a slightly adjusted stop, because in most cases the difference is a few points and the trade thesis remains intact.

The Overlooked MODE_FREEZELEVEL



There's a second, even sneakier problem that doesn't get enough attention: the freeze level.

MODE_FREEZELEVEL is described in the documentation as: "Order freeze level in points. If the execution price lies within the range defined by the freeze level, the order cannot be modified, cancelled or closed."

What this means in practice: you have an open position, price approaches your take profit, and you want to move your stop to breakeven. You call
OrderModify. It fails silently (or with an error you don't bother checking because "it's just a trailing stop"). The reason is that the current price is too close to your take profit, and the server has frozen modifications in that zone.

I've seen EAs that trail stops every tick without checking
MODE_FREEZELEVEL. They burn CPU cycles and API calls, and occasionally get the broker's rate limit warning. The fix is trivial:

`mql4
// Before calling OrderModify for a trailing stop, check freeze level
double freezeLevel = MarketInfo(Symbol(), MODE_FREEZELEVEL)
Point;
if(MathAbs(Bid - OrderTakeProfit()) < freezeLevel)
{
// Too close to TP, skip modification this tick
return;
}
`

An Unconventional Take on OrderSelect Data Freshness



Here's something I've argued about with other developers: the MQL4 documentation says that
OrderSelect copies order data into the program environment, and that subsequent calls to OrderOpenPrice(), OrderStopLoss(), etc., return the copied data, not live data .

The doc then says: "It is strongly recommended to call the OrderSelect() function before request the order data."

Most people read this as "always call OrderSelect before reading order properties." That's correct. But the unconventional implication is this: if you're reading order properties in a loop, you're getting a snapshot from the moment of the last
OrderSelect call, not the current state.

In a fast-moving market, this matters. If you iterate through 10 open orders, selecting each one and checking its profit, the first order's data might be 50 milliseconds stale by the time you reach the tenth. For trailing stops and breakeven logic, that lag can be the difference between locking in profit and giving it back.

My approach: for any order modification decision, I call
OrderSelect immediately before the OrderModify call, even if I already selected the order earlier in the function. The redundancy is worth the safety.

`mql4
// Don't trust the data from the loop iteration.
// Re-select immediately before modifying.
if(OrderSelect(ticket, SELECT_BY_TICKET))
{
double currentSL = OrderStopLoss();
double newSL = CalculateNewStopLoss();

if(newSL != currentSL) // Only modify if actually different
{
bool result = OrderModify(ticket, OrderOpenPrice(), newSL,
OrderTakeProfit(), 0, clrNONE);
if(!result)
Print("Modify failed: ", GetLastError());
}
}
`

The
newSL != currentSL check is there to avoid error 1 (ERR_NO_RESULT), which the documentation explicitly warns about: "If unchanged values are passed as the function parameters, the error 1 (ERR_NO_RESULT) will be generated."

The Real Lesson



MQL4's trading functions are deceptively simple.
OrderSend takes 11 parameters, most of which you can leave at defaults. The documentation is thorough. But the gap between "the documentation says this" and "the broker actually does this" is where EAs break.

The
MODE_STOPLEVEL floating problem is the clearest example. The documentation warns you about it, but in a way that's easy to miss. The fix isn't complex—it's just a matter of reading that one sentence and building your order execution logic around the possibility that the client terminal doesn't have complete information about server-side constraints.

Build your wrapper. Normalize your prices. Check your stop levels. And for the love of all that is holy, call
GetLastError()` after every trading function call.

References:
  • MQL4 Documentation, OrderSend

  • MQL4 Documentation, OrderModify

  • MQL4 Documentation, OrderSelect

  • MQL4 Documentation, MarketInfo / Symbol Properties


  • This article was first published on FXEAR.com. Original content. Unauthorized reproduction prohibited.