Five things that silently break a first EA (and how I check for them)

Most first EAs I look at are not broken because the idea is bad. They are broken
because of a handful of details that never throw an error - the EA just behaves
differently than the author thinks it does. Here are the five I check first.

1. Running the logic on every tick instead of on a new bar

If your entry condition uses the closed candle, it has to be evaluated once per
bar. Inside OnTick without a guard it is evaluated hundreds of times, and the
same signal fires again and again. The usual fix:

 static datetime lastBar = 0;
   datetime curBar = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(curBar == lastBar) return;
   lastBar = curBar;
Symptom when it is missing: far more trades in the tester than the chart
suggests, often several within the same candle.

2. No unique magic number

Without a magic number the EA counts, modifies and closes positions that belong
to you or to another EA on the same account. Set it once and filter by it
everywhere - position loops, history loops, close routines:

   
trade.SetExpertMagicNumber(MagicNumber);


Symptom when it is missing: the EA closes a manual trade you opened yourself, or
two EAs on the same symbol start fighting over the same position.

3. Lot size not normalized to the broker

Every symbol has a volume step, a minimum and a maximum. A calculated lot size
like 0.0374 or a hand-typed 0.15 on a 0.1-step symbol is rejected with error
10014 "Invalid volume" - and the EA usually just logs it and moves on:

   
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   double min  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double max  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   lots = MathFloor(lots / step) * step;
   lots = MathMax(min, MathMin(max, lots));

Symptom when it is missing: works on one broker, sends nothing on another.

4. Points and pips treated as the same thing

On a 5-digit broker one pip is ten points. A stop loss entered as "20" is 2 pips
if the code multiplies by _Point and 20 pips if it multiplies by a pip factor.
The same input then means something completely different per symbol:

  int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double pip = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * ((digits == 5 || digits == 3) ? 10 : 1);

Symptom when it is missing: stops that sit absurdly tight on some symbols and
absurdly wide on others - and gold or indices behave differently again.

5. Testing with the wrong modelling mode

"Open prices only" is fast and tells you almost nothing about an EA that reacts
intraday or uses a stop inside the candle. It cannot know where price went
between open and close, so it guesses. Use "Every tick based on real ticks" for
anything that is not a pure bar-close strategy, and compare the two runs - if
the results differ wildly, the strategy depends on intrabar behaviour and the
faster mode is lying to you.

Symptom when it is wrong: a beautiful equity curve that does not survive a
forward test.

How I work through these

Before I look at the strategy at all, I read the file top to bottom once and
only look for those five things. It takes a few minutes and it removes most of
the "why is it doing that" questions before they come up. Only then do I start
testing the idea itself, because otherwise I am testing the bugs, not the
strategy.

Curious what others add to this list - what breaks most often in the EAs you see?
i would add one more thing here, not checking if the trade request actually worked

a lot of EAs just call Buy(), Sell() or PositionModify() and then continue like everything went fine. but broker can reject it for volume, stops level, filling mode, market state etc

so at least i always check/log the retcode when something fails. otherwise you can waste a lot of time debugging the strategy while the real problem is just execution

also gets more important when you move same EA between forex gold or indices, symbol specs can be very different
Good addition, and an important one.

CTrade returning false tells you that something went wrong but not what, and
most EAs never look even at that. ResultRetcode() and
ResultRetcodeDescription() right after the call cost two lines and save hours.

The filling mode case is the one I see catch people out most often - error
10030 on a broker that only accepts FOK while the EA asks for IOC. Same story
with SYMBOL_TRADE_STOPS_LEVEL when you move a working forex EA to gold, the
minimum distance is suddenly an order of magnitude different.

Your last point is the one people underestimate. The strategy is identical
between symbols, the symbol specs are not, and nothing in the code tells you
that until an order silently fails.
[Deleted]

Good list — I'd add a sixth: when the terminal dies, the EA dies with it. A crash, a dropped connection or a 3am Windows update takes the chart, the timers and every OnTick risk check down together, while the broker keeps working. No logic written inside the EA can run at that moment.

So one layer has to live outside the terminal: a small watchdog that watches the terminal process and the last account event, and treats "no update for N minutes" as a failure instead of a quiet market.

How I test that guard, because untested guards are comforting fiction: with the EA armed, kill terminal64.exe from Task Manager, reopen the chart and check whether the EA refuses to open a new trade until its state is reloaded. If your counters only live in RAM, the restart quietly zeroes them — persist state to disk before anything else.

All of this is risk control, not a prediction — no signal, no performance claim.