Backtest Protocol for a Gold EA: Plateau, Forward, MFE
A gold EA backtest only means something with four checks the equity curve hides: a frozen out-of-sample split, a parameter plateau instead of a peak, per-trade MFE/MAE excursions, and enough trades to separate an edge from a coin.
Gold expert advisors rarely die in the Strategy Tester. They die after it, when the parameter set that printed a 3.4 profit factor on last year's data meets a session that looks nothing like the sample. Costs are settled elsewhere on the EA board — spread, slippage, swap and the tester settings that delete them, plus volatility-normalized sizing and setfile tiers. What is left is the protocol that is supposed to be able to fail you.
1. The optimizer ranks noise unless you count your tries
Every optimization is a multiple-comparison test and MT5 does not correct for it. With zero edge, the best of N combinations still looks impressive, because you selected the luckiest draw. Simulating 400,000 standard-normal draws, the expected best in-sample z-score is 1.94 for 25 combinations, 2.53 for 125 and 3.07 for 625 (p90 3.14 at 125). A 5 x 5 x 5 grid is 125 passes, and a Sharpe of 2.5 from it has discovered its own size. Write N down before the run and discount the winner by it.
2. Split the data before you look at it
The tester has the mechanism and most retail development leaves it off. The Forward option divides the Date range — a half, a third, a quarter or a custom start — optimizes on the first part, then re-tests 10% of the best passes under a full search or 25% under the genetic algorithm on the untouched rest, with a floor of 256 passes advanced to forward testing.
- The forward slice is always the latest part. You cannot hold out the middle, so a single test has a blind spot for early regime change.
- The optimizer never sees it. That value is spent the first time you change the criterion after seeing the result.
- A criterion edited afterwards makes the forward set training data. Freeze the minimum trade count and drawdown cap in writing before the run.
A useful pass rule is forward profit factor of at least 0.7 x in-sample, on at least a third of the trade count. For calibration, an MQL5 walk-forward study over 16 rolling windows scored a genetically optimized FastMA 5 / SlowMA 25 SMA configuration at mean walk-forward efficiency 0.75, against 0.97 for a rigid, never-optimized 10/50 EMA — and both passed on only 37.5% of windows. The optimizer did not lose because EMA crossovers are better; it lost because the parameters it chose fit the calibration period.
3. A plateau, not a peak
Sort the report by your criterion and the top row is a peak in noise. What transfers is a neighbourhood:
- Check the peak's immediate neighbours, one step either side on every optimized axis.
- If they average below roughly 70% of the peak, treat it as a spike and drop it.
- Prefer a cell at 85% of the maximum inside a flat region.
- A cliff — a take-profit multiplier of 2.0 printing 3.4 profit factor while 2.25 prints 0.9 — is a discontinuity in the sample, not an edge.
Gold makes it concrete. The last 14 sessions ran $51.70 to $157.00 of daily range on an ATR(14) of $93.62, and spot closed at $4,028.10 on 10 November, down about 2.7% on the Investing series (USA Today quoted $4,023.02, the usual feed gap). A stop tuned on the quiet end of that range is a different strategy on the loud end — which is why a plateau has to be read across a full volatility cycle.
4. MFE and MAE: what the equity curve never shows
Every trade carries two excursions the report does not print: the worst the position went against you (maximum adverse excursion, MAE) and the best it went for you (MFE). Both are exit-design information.
Measured on 71 gold sessions from 3 August to 10 November 2026, long at each session's open against that day's high and low: the median run against the position was $35.26, p90 $91.11, worst $166.95. Favourable excursion was near-symmetrical — median $35.90, p90 $95.24. A 0.7 x ATR stop at $65.53 would have been tagged on 15 of 71 sessions (21%), and in 63 of 71 the day never offered a full ATR of favourable movement at all.
- A 40% win rate can be a stop-placement artefact. If median MAE is $35 and the stop sits at $25, plenty of losers would have reached the target with a noise-appropriate stop.
- Log both per trade. Then the only exit question — at what multiple of ATR does the trade stop coming back? — has data behind it.
5. Sixty trades is not a sample
Win-rate standard error is the square root of p(1-p)/n: at a 50% rate and 30 trades that is 9.1 points, or plus or minus 17.9 at 95%; at 100 trades, plus or minus 9.8. Two backtests showing 52% and 44% over 50 trades each are, statistically, the same result. Run the parameter count against the trade count too: five optimized parameters and 40 trades is eight observations per knob. Either cut the grid or lengthen the sample; keeping both does not work.
6. The criterion and the log
The criterion should refuse to score a run that is not a sample, so a 12-trade pass cannot win an optimization:
input int InpMinTrades = 60; // excluded from the optimization itself
input double InpMaxDDPct = 12.0;
double OnTester()
{
double trades = TesterStatistics(STAT_TRADES);
double profit = TesterStatistics(STAT_PROFIT);
double ddPct = TesterStatistics(STAT_EQUITY_DDREL_PERCENT);
if(trades < InpMinTrades) return 0.0; // not a sample, not a score
if(ddPct > InpMaxDDPct) return 0.0; // risk outside the mandate
if(profit <= 0.0) return 0.0;
return (profit / ddPct) * MathSqrt(trades / InpMinTrades);
}The return value is what the tester ranks as Custom max; the statistics are the documented TesterStatistics() identifiers and the sorting rule is in the OnTester reference. It also runs on a single test, which is how you validate the formula against a known pass.
The excursion log is a few lines called from OnTick():
double g_mfe = 0.0, g_mae = 0.0;
void TrackExcursion()
{
if(!PositionSelect(_Symbol)) return;
double open = PositionGetDouble(POSITION_PRICE_OPEN);
double dir = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? 1.0 : -1.0;
double px = (dir > 0.0) ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
: SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double exc = dir * (px - open);
if(exc > g_mfe) g_mfe = exc;
if(exc < g_mae) g_mae = exc;
//--- reset both in OnTradeTransaction once the closing deal is written
}Divide both by ATR at entry and the figures compare across regimes: $65.53 is 0.7 in those units whatever gold is doing that month.
7. The order to run it in
- Freeze the split, the criterion inputs and the parameter count before the first pass.
- Optimize in-sample only; full search for grids under a few thousand passes.
- Forward-run once, compare profit factor and trade count, then leave the criterion alone.
- Export the deal list, compute MAE and MFE in ATR units, and set the stop from that distribution.
- Only then go to a demo on the broker's own feed and check fills against the order-request rules — mechanics in the backtesting topic hub.
None of this makes a bad strategy good. It makes a fitted one visibly fitted, which is all a backtest can honestly do.
Sources: MetaTrader 5 optimization and forward testing and tick generation docs; MQL5 OnTester, TesterStatistics and the walk-forward efficiency study; USA Today, 10 November.
Not financial advice. Backtests and optimizations are not live results; historical fits do not transfer, and no parameter set shown here is a recommendation to trade.