Five Structural Failures I Find in Most "Working" EAs

Five Structural Failures I Find in Most "Working" EAs

27 June 2026, 19:24
Boris Armenteros
0
23

After 14 years of reviewing and rescuing Expert Advisors — across 30+ code reviews over the past three years alone — the structural problems repeat with remarkable consistency. This is not about strategy logic. The strategies are often fine. The failures are in the plumbing: error handling, state management, environment assumptions, execution validation, and defensive guards.

Here are the five patterns I encounter most often, with MQL4 code examples showing both the problem and the fix.

1. No Error Handling on OrderSend

The EA sends a trade request and never checks what happened:

// Problem: no return value check
OrderSend(Symbol(), OP_BUY, 0.10, Ask, 3, sl, tp);
gridLevel++;  // Increments regardless of whether the order filled

In the Strategy Tester, every order fills. In live trading, requotes, margin failures, and trade context busy conditions cause silent failures. The EA's internal state diverges from reality.

// Fix: check return value, verify, then update state
int ticket = OrderSend(Symbol(), OP_BUY, 0.10, Ask, 3, sl, tp);
if(ticket < 0)
{
   printf("%s: OrderSend failed | Error=%d", __FUNCTION__, GetLastError());
   return;
}
gridLevel++;  // Only increment after confirmed fill

I fixed a grid EA where the counter incremented on every OrderSend()  call regardless of the result. In live trading, requotes during volatile sessions created phantom grid levels. The average entry price in the EA's memory diverged from the account's actual average.

2. No State Persistence Across Restarts

Most EAs I rescue store all state in runtime variables:

// Problem: static variable resets on terminal restart
static int multiplier = 1;

Terminal restarts constantly — VPS reboots, MetaTrader updates, broker disconnects. Every restart fires OnDeinit()  then OnInit() , and every static  variable resets to its initial value.

A martingale EA I fixed tracked its multiplier this way. After a weekend restart during a drawdown, the multiplier reset to 1. The EA opened a base lot position while the losing chain was still open.

// Fix: persist to file and reconcile on init
void SaveState()
{
   int handle = FileOpen("ea_state.bin", FILE_WRITE|FILE_BIN);
   if(handle != INVALID_HANDLE)
   {
      FileWriteInteger(handle, multiplier);
      FileWriteInteger(handle, cycleCount);
      FileClose(handle);
   }
}

int OnInit()
{
   // Reconcile: rebuild state from open orders + persisted data
   int handle = FileOpen("ea_state.bin", FILE_READ|FILE_BIN);
   if(handle != INVALID_HANDLE)
   {
      multiplier = FileReadInteger(handle);
      cycleCount = FileReadInteger(handle);
      FileClose(handle);
   }
   // Cross-check against actual open orders
   ReconcileWithBrokerState();
   return INIT_SUCCEEDED;
}

The global variable pool ( GlobalVariableSet , GlobalVariableGet ) also survives restarts and is simpler for single-value persistence.

3. Hardcoded Environment Assumptions

Point * 10  is correct for 5-digit forex pairs. It is wrong for gold, indices, and 4-digit brokers:

// Problem: hardcoded pip calculation
double pipSize = Point * 10;  // Wrong on XAUUSD, wrong on 4-digit brokers
double minLot = 0.01;         // Fails on brokers with 0.10 minimum
// Fix: query symbol properties at runtime
double pipSize = Point * MathPow(10, Digits % 2);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
int stopLevel = (int)MarketInfo(Symbol(), MODE_STOPLEVEL);

Every hardcoded value in the first block has broken an EA I have rescued. Every runtime query in the second block would have prevented it.

4. Ignoring Execution Feedback

Beyond OrderSend() , most EAs never check OrderModify() , OrderClose() , or OrderDelete()  return values:

// Problem: fire and forget
OrderModify(ticket, price, newSL, tp, 0);
printf("SL updated to %.5f", newSL);  // Logs intent, not result

I fixed a trailing stop EA where OrderModify()  was rejected every tick with error 130 (invalid stops) because the new SL violated MODE_STOPLEVEL . The EA logged "trailing active" while the stop loss had not moved in hours.

// Fix: pre-validate and log effective values
double stopsLevel = MarketInfo(Symbol(), MODE_STOPLEVEL) * Point;
double effectiveSL = MathMin(newSL, Bid - stopsLevel);

if(effectiveSL > currentSL + minDelta)
{
   if(OrderModify(ticket, price, effectiveSL, tp, 0))
   {
      printf("%s: SL modified | Effective=%.5f Requested=%.5f",
             __FUNCTION__, effectiveSL, newSL);
   }
   else
   {
      printf("%s: OrderModify failed | Error=%d", __FUNCTION__, GetLastError());
   }
}

5. No Defensive Guards

The final pattern: no bounds checking, no zero-denominator guards, no validation of indicator return values.

// Problem: ATR can return zero on thin markets
double sl = iATR(Symbol(), 0, 14, 0);
double lots = riskAmount / (sl / Point);  // Division by zero when ATR = 0

I worked on an EA where this exact scenario produced an infinite lot size during a news spike. NormalizeDouble()  silently converted infinity to a very large number. The broker rejected the order on lot limits — the only thing that prevented a catastrophic fill.

// Fix: guard every denominator and array access
if(Bars < requiredBars)
{
   printf("%s: Insufficient history | Bars=%d Required=%d",
          __FUNCTION__, Bars, requiredBars);
   return;
}

double sl = iATR(Symbol(), 0, 14, 0);
if(sl <= 0)
{
   printf("%s: ATR returned zero | Skipping", __FUNCTION__);
   return;
}

double lots = NormalizeDouble(riskAmount / (sl / Point), 2);
lots = MathMax(lots, MarketInfo(Symbol(), MODE_MINLOT));
lots = MathMin(lots, MarketInfo(Symbol(), MODE_MAXLOT));

The Pattern

All five failures share the same root cause: the EA was tested only in the Strategy Tester — the environment where everything works. Orders fill instantly. Terminals never restart. Symbol properties match the developer's setup. History is complete.

Live trading is the environment where things fail. Production-ready code assumes that every trade request can be rejected, every stored value can be lost, and every assumption about the broker can be wrong.

Five questions to check any EA:

  1. Does it check return values on every OrderSend , OrderModify , and OrderClose ?
  2. Does it persist critical state and reconcile on startup?
  3. Does it query symbol properties at runtime?
  4. Does it validate execution feedback and log effective values?
  5. Does it guard against zero denominators and insufficient history?

If any answer is "no," the backtest results are fiction with a delayed fuse.