SP500 Expert Advisor MT5: US500 MQL5 EA Code & Backtest Guide
This page provides a complete MQL5 Expert Advisor for trading the S&P 500 index (US500) on MetaTrader 5, implementing a trend-following strategy built around moving average crossovers and ATR-based risk management. You will find production-ready EA source code, a detailed backtest report covering 2020–2025, and a practical guide to optimizing the system for varying market regimes on the US500 symbol.
Backtest Performance
Past performance is not indicative of future results. Backtest statistics are based on historical data and do not guarantee future profits. Trading involves significant risk of loss. This content is for educational purposes only and does not constitute financial advice.
Strategy Logic
Entry Conditions
The EA enters a long position when the 20-period EMA crosses above the 50-period EMA on the H1 chart, confirmed by the closing price sitting above both moving averages, signalling that short-term momentum is aligned with the intermediate trend. A short entry triggers on the mirror condition — 20 EMA crossing below the 50 EMA with price below both lines. All entry signals are filtered by an ADX threshold of 20 to avoid trading in low-momentum, sideways conditions that are unfavourable for trend-following on equity indices.
Exit Conditions
Each trade is assigned a fixed stop-loss set at 1.5x the 14-period ATR below the entry candle low for longs (above the high for shorts), ensuring position sizing adapts automatically to current US500 volatility. The take-profit target is placed at 2.5x ATR from entry, giving the strategy a reward-to-risk ratio of approximately 1.67. A trailing stop kicks in once the trade is 1x ATR in profit, locking in gains during extended S&P 500 trending moves while leaving room for the position to breathe.
MQL5 Expert Advisor Code
//+------------------------------------------------------------------+
//| SP500 Trend-Following Expert Advisor for MetaTrader 5 |
//| Symbol : US500 (S&P 500 CFD) | Timeframe : H1 |
//| Strategy: EMA crossover + ADX filter + ATR risk management |
//+------------------------------------------------------------------+
#property copyright "Pineify – pineify.app"
#property version "1.00"
#property strict
//--- Input parameters
input int FastEMA_Period = 20; // Fast EMA period
input int SlowEMA_Period = 50; // Slow EMA period
input int ADX_Period = 14; // ADX period
input double ADX_Threshold = 20.0; // Minimum ADX to trade
input int ATR_Period = 14; // ATR period
input double SL_ATR_Mult = 1.5; // Stop-loss ATR multiplier
input double TP_ATR_Mult = 2.5; // Take-profit ATR multiplier
input double Trail_ATR_Mult = 1.0; // Trailing stop activation (ATR)
input double RiskPercent = 1.0; // Risk per trade (% of account)
input int MagicNumber = 202305; // EA magic number
input string TradeComment = "SP500_TF_EA";
//--- Indicator handles
int handleFastEMA;
int handleSlowEMA;
int handleADX;
int handleATR;
//--- Global state
datetime lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialisation |
//+------------------------------------------------------------------+
int OnInit()
{
handleFastEMA = iMA(_Symbol, PERIOD_H1, FastEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
handleSlowEMA = iMA(_Symbol, PERIOD_H1, SlowEMA_Period, 0, MODE_EMA, PRICE_CLOSE);
handleADX = iADX(_Symbol, PERIOD_H1, ADX_Period);
handleATR = iATR(_Symbol, PERIOD_H1, ATR_Period);
if(handleFastEMA == INVALID_HANDLE ||
handleSlowEMA == INVALID_HANDLE ||
handleADX == INVALID_HANDLE ||
handleATR == INVALID_HANDLE)
{
Print("Failed to create indicator handles. Error: ", GetLastError());
return INIT_FAILED;
}
Print("SP500 Trend-Following EA initialised. Symbol=", _Symbol,
" Magic=", MagicNumber);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| Expert deinitialisaton |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IndicatorRelease(handleFastEMA);
IndicatorRelease(handleSlowEMA);
IndicatorRelease(handleADX);
IndicatorRelease(handleATR);
Print("SP500 EA deinitialized. Reason code: ", reason);
}
//+------------------------------------------------------------------+
//| Helper: count open positions for this EA |
//+------------------------------------------------------------------+
int CountPositions(ENUM_POSITION_TYPE posType)
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetSymbol(i) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == MagicNumber &&
PositionGetInteger(POSITION_TYPE) == posType)
count++;
}
return count;
}
//+------------------------------------------------------------------+
//| Helper: lot size from risk percent |
//+------------------------------------------------------------------+
double CalcLotSize(double slPoints)
{
double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double riskAmount = accountEquity * RiskPercent / 100.0;
if(slPoints <= 0 || tickValue <= 0 || tickSize <= 0)
return SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double pointValue = tickValue / tickSize;
double lots = riskAmount / (slPoints * pointValue);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lots = MathFloor(lots / lotStep) * lotStep;
lots = MathMax(minLot, MathMin(maxLot, lots));
return lots;
}
//+------------------------------------------------------------------+
//| Helper: send a market order |
//+------------------------------------------------------------------+
bool PlaceOrder(ENUM_ORDER_TYPE orderType, double sl, double tp, double lots)
{
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = lots;
request.type = orderType;
request.price = (orderType == ORDER_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
request.sl = sl;
request.tp = tp;
request.deviation = 20;
request.magic = MagicNumber;
request.comment = TradeComment;
request.type_filling = ORDER_FILLING_IOC;
if(!OrderSend(request, result))
{
Print("OrderSend failed. Retcode=", result.retcode,
" Desc=", result.comment);
return false;
}
Print("Order placed. Ticket=", result.order, " Type=",
EnumToString(orderType), " Lots=", lots,
" SL=", sl, " TP=", tp);
return true;
}
//+------------------------------------------------------------------+
//| Manage trailing stop for open positions |
//+------------------------------------------------------------------+
void ManageTrailingStop(double atr)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetSymbol(i) != _Symbol) continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue;
ulong ticket = PositionGetInteger(POSITION_TICKET);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double curSL = PositionGetDouble(POSITION_SL);
ENUM_POSITION_TYPE pType =
(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
if(pType == POSITION_TYPE_BUY)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double newSL = bid - Trail_ATR_Mult * atr;
newSL = NormalizeDouble(newSL, _Digits);
if(newSL > curSL && bid >= openPrice + Trail_ATR_Mult * atr)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.symbol = _Symbol;
req.sl = newSL;
req.tp = PositionGetDouble(POSITION_TP);
OrderSend(req, res);
}
}
else if(pType == POSITION_TYPE_SELL)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double newSL = ask + Trail_ATR_Mult * atr;
newSL = NormalizeDouble(newSL, _Digits);
if((curSL == 0 || newSL < curSL) &&
ask <= openPrice - Trail_ATR_Mult * atr)
{
MqlTradeRequest req = {};
MqlTradeResult res = {};
req.action = TRADE_ACTION_SLTP;
req.position = ticket;
req.symbol = _Symbol;
req.sl = newSL;
req.tp = PositionGetDouble(POSITION_TP);
OrderSend(req, res);
}
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Only process logic on a new H1 bar
datetime currentBarTime =
(datetime)SeriesInfoInteger(_Symbol, PERIOD_H1, SERIES_LASTBAR_DATE);
if(currentBarTime == lastBarTime) return;
lastBarTime = currentBarTime;
//--- Copy indicator buffers (index 0 = current completed bar)
double fastEMA[2], slowEMA[2], adxMain[2], atrBuf[2];
if(CopyBuffer(handleFastEMA, 0, 1, 2, fastEMA) < 2) return;
if(CopyBuffer(handleSlowEMA, 0, 1, 2, slowEMA) < 2) return;
if(CopyBuffer(handleADX, 0, 1, 2, adxMain) < 2) return;
if(CopyBuffer(handleATR, 0, 1, 2, atrBuf) < 2) return;
double fastNow = fastEMA[1], fastPrev = fastEMA[0];
double slowNow = slowEMA[1], slowPrev = slowEMA[0];
double adxNow = adxMain[1];
double atrNow = atrBuf[1];
//--- Trailing stop management (runs every bar)
ManageTrailingStop(atrNow);
//--- Signal detection
bool crossUp = (fastPrev <= slowPrev) && (fastNow > slowNow);
bool crossDown = (fastPrev >= slowPrev) && (fastNow < slowNow);
bool adxOk = adxNow >= ADX_Threshold;
//--- Close bar price
double closeBar[];
if(CopyClose(_Symbol, PERIOD_H1, 1, 1, closeBar) < 1) return;
double closeNow = closeBar[0];
//--- Long entry
if(crossUp && adxOk && closeNow > fastNow && closeNow > slowNow)
{
if(CountPositions(POSITION_TYPE_BUY) == 0 &&
CountPositions(POSITION_TYPE_SELL) == 0)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = NormalizeDouble(ask - SL_ATR_Mult * atrNow, _Digits);
double tp = NormalizeDouble(ask + TP_ATR_Mult * atrNow, _Digits);
double slPts = (ask - sl) / _Point;
double lots = CalcLotSize(slPts);
PlaceOrder(ORDER_TYPE_BUY, sl, tp, lots);
}
}
//--- Short entry
if(crossDown && adxOk && closeNow < fastNow && closeNow < slowNow)
{
if(CountPositions(POSITION_TYPE_BUY) == 0 &&
CountPositions(POSITION_TYPE_SELL) == 0)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = NormalizeDouble(bid + SL_ATR_Mult * atrNow, _Digits);
double tp = NormalizeDouble(bid - TP_ATR_Mult * atrNow, _Digits);
double slPts = (sl - bid) / _Point;
double lots = CalcLotSize(slPts);
PlaceOrder(ORDER_TYPE_SELL, sl, tp, lots);
}
}
}
//+------------------------------------------------------------------+Copy this code into MetaEditor (F4 in MT5), save in the Experts folder, and compile with F7.
Generate a Custom SP500 Trend-following EA →
Pineify AI generates syntactically validated MQL5 Expert Advisors from plain English descriptions. Customize entry logic, risk management, and trading sessions — no coding required.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Bar-based, backtesting only | Tick-based, live trading |
| Deployment | TradingView alerts | Runs 24/5 on VPS/MT5 |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Built-in, no data download needed | Strategy Tester, tick data required |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
Pineify supports both platforms. Prototype your strategy visually in TradingView Pine Script, then generate the equivalent MQL5 EA for live MT5 trading.