Backtest accuracy in MT4 is often compromised by two hidden issues: accidental future function usage and unrealistic order execution simulation. This article provides advanced techniques to eliminate both, targeting experienced EA developers.
1. Future Function Detection Formula
A future function occurs when an EA uses data from the current bar's close before the bar actually closes. Common offenders: `Close[0]` or `Open[0]` inside `start()` without volume-based confirmation.
Mathematical definition of future leakage:
`Signal(t) = f(Price(t), Volume(t))` where `Volume(t)` is incomplete at time `t`.
To detect, implement timestamp check:
```cpp
datetime lastBarTime = 0;
bool IsNewBar() {
if(Time[0] != lastBarTime) {
lastBarTime = Time[0];
return true;
}
return false;
}
```
If your EA uses `Close[0]` before `IsNewBar()` returns true, it's a future function.
2. Safe Bar Access Pattern
Instead of `Close[0]`, use only confirmed closed bars:
```cpp
double getConfirmedClose(int shift) {
if(shift == 0) {
if(IsNewBar()) return Close[1];
else return EMPTY_VALUE; // not confirmed
}
return Close[shift];
}
```
This forces the EA to trade only on bar close or next bar open, eliminating look-ahead bias.
3. Order-Fill Simulation with Spread & Volume
MT4's default backtest fills orders at any price within the bar. Real market requires volume and spread constraints. Add realistic fill condition:
```cpp
bool CanOpenAtPrice(double price, int direction) {
double currentSpread = Ask - Bid;
double slippage = currentSpread * 0.3; // 30% spread slippage
double volumeAtPrice = iVolume(Symbol(), PERIOD_M1, 0);
if(direction == OP_BUY) {
if(volumeAtPrice < 10) return false; // low volume, skip
if(price < Ask + slippage) return true;
} else if(direction == OP_SELL) {
if(volumeAtPrice < 10) return false;
if(price > Bid - slippage) return true;
}
return false;
}
```
4. Tick Simulation for Limit Orders
MT4 backtest uses only OHLC data by default. To simulate tick-level limit order fills, override `OrderSend()` wrapper:
```cpp
int RealisticOrderSend(int cmd, double price, double sl, double tp) {
int ticket = OrderSend(Symbol(), cmd, 0.1, price, 0, sl, tp, "EA", magic);
if(ticket > 0 && cmd == OP_BUYLIMIT) {
// Simulate tick test: limit order fills only if price touches
double highAfter = iHigh(Symbol(), PERIOD_M1, 0);
if(highAfter < price) {
OrderDelete(ticket); // not filled, remove
return -1;
}
}
return ticket;
}
```
5. Backtest Accuracy Metrics
Calculate realistic profit factor adjusted for slippage:
`AdjProfitFactor = (GrossProfit - SlippageCost) / (GrossLoss + SlippageCost)`
Where `SlippageCost = TotalTrades * AvgSpread * 0.5` (half-spread assumption).
6. Parameter Validation Before Optimization
Before running MT4 optimization, validate parameters using walk-forward matrix:
```cpp
double ValidateParamSet(double param1, double param2) {
double inSampleProfit = RunBacktest(StartDate, MidDate);
double outSampleProfit = RunBacktest(MidDate, EndDate);
double stability = 1 - fabs(inSampleProfit - outSampleProfit) / fabs(inSampleProfit);
return stability * (inSampleProfit + outSampleProfit) / 2;
}
```
Avoid parameters where `stability < 0.7` – they indicate overfitting.
Reference