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.
Historical test results
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, save it in the MQL5/Experts folder, and compile with F7.
Generate a custom SP500 trend-following EA
Describe the ATR inputs, alert rules, visual style, or confirmation filter you want. Pineify generates editable MQL5 source code that you can inspect and compile in MetaEditor.
Pine Script vs MQL5: Same Strategy, Different Platforms
| Aspect | Pine Script (TradingView) | MQL5 (MetaTrader 5) |
|---|---|---|
| Execution | Series evaluated on chart updates | Event handlers with explicit buffers |
| Deployment | Runs in TradingView with alerts | Runs in the MT5 terminal or on a VPS |
| Broker access | Via TradingView broker integration | Direct broker connectivity |
| Backtesting | Strategy Tester for strategy scripts | Strategy Tester for Expert Advisors |
| Code complexity | Simpler, functional syntax | C++-like, more powerful |
The calculation can be implemented on either platform, but the runtime model and buffer APIs differ. Read the Pine Script Supertrend reference before porting logic between them.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
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.