◆ New — Strategy Validator, Code Fixer & Prop Compliance Checker are live. Try one free →
Home / Learn / Why Expert Advisors Blow Accounts: The Bugs to Check Before You Run One
MetaTrader

Why Expert Advisors Blow Accounts: The Bugs to Check Before You Run One

Most of the ways an expert advisor blows an account are not exotic market events — they are ordinary code bugs that compile fine, pass a clean backtest, and only bite once real money and real spread are involved. This guide walks through the six safety bugs to check before you run any bought, downloaded, or AI-generated EA, and shows how to spot each one in the code. It is educational only, not financial advice; trading carries a substantial risk of loss, and you should always test on a demo account first.

Why a "working" EA can still be dangerous

An expert advisor is just a program that places orders for you. That is the whole appeal — and the whole problem. When you run someone else's EA, you are handing your account to code you did not write, cannot always read, and usually cannot test under the exact conditions that will eventually occur. The uncomfortable truth is that most of the ways an expert advisor blows an account have nothing to do with a bad trading idea. They come from plain software bugs: missing safety checks that a compiler happily accepts and a backtest never punishes.

This matters because "it compiled" and "it looked great in the Strategy Tester" feel like proof, and they are not. A backtest runs on historical data at whatever modelling quality you selected, often without realistic spread, slippage, or requotes. A bug that only triggers on a real tick, a real gap, or a rejected order simply does not show up. So the EA passes every check a casual user knows how to run, and then behaves very differently the first busy Monday open.

The good news is that the dangerous bugs are a short, knowable list, and you can look for each one directly in the source. The rest of this article walks through six of them: no stop-loss, over-trading every tick, unchecked trade results, a missing magic number, hardcoded lot sizes, and duplicate entries. None of this is fearmongering — plenty of EAs are written carefully. The point is to give you a checklist so you can tell the careful ones from the careless ones before you risk a cent. This is educational information only, not financial advice, and no code review removes the risk of loss.

Bug 1 — Market orders with no real stop-loss

The single most account-ending bug is an order sent with no stop-loss, or with a stop-loss of 0.0, which MetaTrader treats as "no stop" at all. Without a hard stop on the order itself, a single trade can run against you indefinitely. Some EAs claim to manage risk "internally" — closing the trade when a condition is met — but if that logic never fires (a bug, a disconnect, a terminal that is switched off), there is nothing on the broker's side to cap the loss. A stop-loss attached to the order lives on the broker's server and does its job even if your machine is asleep.

When you read the code, find every place an order is opened and confirm a genuine non-zero stop-loss price is passed. In MQL5 that means the sl argument of the trade call is a real price a sensible distance from entry, derived from something like ATR or a fixed point distance — not 0, and not a value so tight it sits inside the spread. Be suspicious of any EA whose entry function takes no stop parameter at all.

Here is the shape a safe MQL5 entry should have — note the stop-loss is computed and verified, never left at zero:

5
#include <Trade/Trade.mqh>
CTrade trade;

input double InpLots     = 0.10;     // lot size from an input, never a silent literal
input int    InpStopPts  = 300;      // stop distance in points
input ulong  InpMagic    = 20240607; // unique, non-zero magic number

void OpenBuy()
{
   double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double sl    = ask - InpStopPts * point;   // REAL non-zero stop-loss
   double tp    = ask + InpStopPts * point;   // 1:1 example target

   trade.SetExpertMagicNumber(InpMagic);
   if(!trade.Buy(InpLots, _Symbol, ask, sl, tp))
   {
      PrintFormat("Buy send failed, retcode=%d", trade.ResultRetcode());
      return;
   }
   if(trade.ResultRetcode() != TRADE_RETCODE_DONE)
      PrintFormat("Buy not filled cleanly, retcode=%d", trade.ResultRetcode());
}

If you cannot find a real stop being set like this, treat that as a stop sign, not a detail to fix later. An EA that opens positions with no protective stop is the most common reason a compiled, backtested strategy still drains a live account.

Bug 2 — Trading every tick instead of every bar

The OnTick() function runs on every incoming price change — potentially many times per second on an active pair. If an EA evaluates its entry logic on every tick without gating to a new bar, two bad things happen. First, the same signal is read over and over within a single candle, so the EA can fire a burst of orders (or thrash a position open and closed) on what a human would see as one setup. Second, it reads the current, unfinished bar (shift 0), whose high, low, and close are still moving — a signal that looks true mid-candle can vanish by the close. This is a classic source of over-trading and of results that look great in a backtest but fall apart live.

The fix is a new-bar gate: remember the timestamp of the last processed bar, and only run the logic when a new bar has actually opened. Alongside that, read your confirmed signal from the last closed bar (shift 1), not the live bar (shift 0), so the value you act on cannot change after you act. When you review an EA, search for OnTick and check whether the first thing it does is a new-bar check. If the entry logic runs unconditionally on every tick and references index 0, expect over-trading and unstable behaviour.

In MQL5, remember that iMA() returns an indicator handle, not a price — you create the handle once (in OnInit) and then copy confirmed values out of its buffer. The example below reads the EMA and the close at the last closed bar (shift 1) and compares them for a stable, closed-bar condition:

5
datetime g_lastBar   = 0;
int      g_emaHandle = INVALID_HANDLE;

int OnInit()
{
   g_emaHandle = iMA(_Symbol, _Period, 21, 0, MODE_EMA, PRICE_CLOSE);
   if(g_emaHandle == INVALID_HANDLE)
      return INIT_FAILED;
   return INIT_SUCCEEDED;
}

bool IsNewBar()
{
   datetime t = iTime(_Symbol, _Period, 0);   // open time of current bar
   if(t == g_lastBar) return false;           // same bar, do nothing
   g_lastBar = t;
   return true;
}

void OnTick()
{
   if(!IsNewBar()) return;                     // gate: run once per bar

   double ema[];
   // copy the EMA value at the last CLOSED bar (shift 1)
   if(CopyBuffer(g_emaHandle, 0, 1, 1, ema) != 1) return;

   double emaPrev   = ema[0];
   double closePrev = iClose(_Symbol, _Period, 1);  // confirmed close, shift 1

   // evaluate a stable, closed-bar condition, then act
   if(closePrev > emaPrev)
   {
      // ... call your guarded entry routine here (see Bug 1 and Bug 4) ...
   }
}

Gating to a new bar will not make a losing idea profitable — nothing does — but it removes an entire class of accidental over-trading and "why did it take ten trades on one candle" behaviour that quietly bleeds an account through spread and commission.

Bug 3 — Never checking whether the trade actually worked

Sending an order is a request, not a guarantee. The broker can reject it — off-quotes, insufficient margin, market closed, invalid stops, a requote. A careless EA fires the order and moves on as if it succeeded. Now the EA's internal idea of its state ("I am in a long position") disagrees with reality ("the order was rejected and I have no position"). From there, behaviour becomes unpredictable: it may skip a stop it thinks is already placed, or repeatedly retry and pile up orders when one finally goes through.

Every order must be followed by a check of the result, and the code should react to failure — log it, retry sensibly, or stop — rather than assume success. In MQL5 the pattern is to inspect the return of the trade call and the retcode; a genuine fill returns TRADE_RETCODE_DONE. In legacy MQL4 the equivalent is checking whether OrderSend() returned a value less than zero and then reading GetLastError(). Note these are two different dialects — a correct EA picks one and stays in it; mixing MQL4-style OrderSend positional calls with MQL5 CTrade is itself a red flag that the code was pasted together.

The example below passes a real non-zero stop-loss and treats anything other than a clean fill as failure:

5
#include <Trade/Trade.mqh>
CTrade trade;

bool SafeBuy(double lots, double sl, double tp)
{
   // price 0.0 tells CTrade to use the current market price for a market order
   if(!trade.Buy(lots, _Symbol, 0.0, sl, tp))
   {
      PrintFormat("Order send failed: retcode=%d (%s)",
                  trade.ResultRetcode(), trade.ResultRetcodeDescription());
      return false;                                  // do NOT assume success
   }
   if(trade.ResultRetcode() != TRADE_RETCODE_DONE)
   {
      PrintFormat("Order not filled cleanly: retcode=%d", trade.ResultRetcode());
      return false;
   }
   return true;                                       // confirmed done
}

When you read an EA, look at what happens immediately after each order call. If the return value is ignored — if there is no retcode check, no error logging, no branch for failure — the EA is flying blind whenever the broker says no, and busy market opens are exactly when brokers say no.

Bug 4 — No magic number, hardcoded lots, and duplicate entries

Three smaller bugs travel together because they all come from an EA not keeping track of its own trades properly, and any one of them can escalate a normal drawdown into a blown account.

Missing magic number. A magic number is a unique ID an EA stamps on its orders so it can recognise its own positions and ignore everything else. Without one (magic left at 0, or never set), the EA cannot reliably tell its trades apart from your manual trades or another EA's. It may then "manage" — modify or close — positions it never opened, or fail to see its own. Confirm the EA sets a unique, non-zero magic and filters every position lookup by it. In MQL5 that means comparing PositionGetInteger(POSITION_MAGIC) to the expected value before touching a position.

Hardcoded lot size. Look for the volume passed to the order. If it is a literal buried in the code — trade.Buy(1.0, ...) — rather than an input you control, you cannot size to your account, and an EA sized for a large account will over-leverage a small one instantly. Lot size should always come from an input (or a risk-percentage calculation), never a silent constant.

Duplicate entries. Many EAs are written assuming one position at a time, but never actually check before opening. On a fast signal, or after the tick/new-bar bug above, they open a second, third, and fourth position on the same setup — stacking risk far beyond what the strategy intended. The guard is simple: count existing positions for this symbol and magic, and refuse to open if one already exists.

The helper below selects each position before reading its properties, filters by symbol and magic, and refuses to stack. It takes lots and a real stop-loss as inputs and verifies the result:

5
bool HasOpenPosition(ulong magic)
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);   // selects the position
      if(ticket == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
         (ulong)PositionGetInteger(POSITION_MAGIC) == magic)
         return true;                        // already in a trade -> do not stack
   }
   return false;
}

void TryEnter(ulong magic, double lots, double sl, double tp)
{
   if(sl <= 0.0)            return;           // never trade without a real stop
   if(HasOpenPosition(magic)) return;         // duplicate-entry guard

   trade.SetExpertMagicNumber(magic);
   if(!trade.Buy(lots, _Symbol, 0.0, sl, tp)) // price 0.0 = current market price
   {
      PrintFormat("Entry failed, retcode=%d", trade.ResultRetcode());
      return;
   }
   if(trade.ResultRetcode() != TRADE_RETCODE_DONE)
      PrintFormat("Entry not filled cleanly, retcode=%d", trade.ResultRetcode());
}

An EA that sets a magic number, takes its lot size from an input, and checks for an existing position before every entry is showing you it was written by someone who thought about state. The absence of all three usually means it was not.

A practical pre-flight checklist (and how to test safely)

You do not need to be a professional developer to run this check. Open the .mq5 (or .mq4) source in MetaEditor and work down a short list. Reading for these specific bugs is far more informative than another backtest, because the backtest is exactly the environment where these bugs hide — most of the time an expert advisor blow account story starts with one of these checkboxes left unticked.

  • Stop-loss: Does every order pass a real, non-zero stop-loss price? Is it a sane distance, not zero and not inside the spread?
  • New-bar gate: Does OnTick() gate to a new bar before evaluating entries, and read signals from the closed bar (shift 1), not the live bar (shift 0)?
  • Result checks: Is every order followed by a retcode/error check with a real reaction to failure, not silent assumption of success?
  • Magic number: Is a unique, non-zero magic set, and is every position lookup filtered by it?
  • Lot size: Does volume come from an input or a risk calculation, never a hardcoded literal?
  • Duplicate guard: Does the EA check for an existing position before opening a new one?
  • One dialect: Is the code cleanly MQL4 or MQL5, not a mix of OrderSend-positional and CTrade styles pasted together?

If several of these are missing, that is your answer — you do not have to run it to know it is unsafe. If the code looks clean, you still test it the boring way, in this order: run it in the Strategy Tester with the highest modelling quality and realistic spread; then run it on a demo account for a meaningful stretch across different market conditions, including a news day and a session open; and only then, if you choose to go further, on a small live account you can afford to lose entirely, with a hard stop on every trade. Manual code review, careful backtesting, and demo trading catch different problems, so use all three rather than trusting any one.

If reading MQL is genuinely beyond you, that is a reasonable place to lean on tooling — an automated auditor such as ForexCodes' EA Auditor exists precisely to flag missing stops, unchecked results, and duplicate-entry risk before you run the code. But no tool, and no checklist, changes the underlying reality: this is educational information, not financial advice; past behaviour does not predict future results; and every strategy — well-coded or not — carries a real risk of loss. Test on a demo first, size small, and never run an EA you would not be comfortable losing money to.

Key takeaways

Educational only — not financial advice. Trading involves substantial risk of loss.

Catch the bug that compiles.Run auditGet Pro