SP500 Expert Advisor MT5: US500 MQL5 EA Code & Backtest Guide
Inspect a complete educational H1 EMA/ADX Expert Advisor for a broker-provided S&P 500 instrument. The example estimates initial stop-distance risk using OrderCalcProfit and rounds volume down to the symbol step. It is a starting point for contract-specific testing, not production-ready code or a verified 2020–2025 performance report.
Strategy logic
Entry conditions
A completed 20/50 EMA cross, a close beyond both EMAs and ADX at or above the threshold qualify a direction. No entry is allowed while a matching symbol-and-magic position exists. Decisions occur once per H1 bar after data is ready; a rejected request is not retried as a new entry within that same bar.
Exit conditions
Initial SL is 1.5 ATR from the entry quote, not the candle low/high; TP is 2.5 ATR. The nominal distance ratio is 2.5/1.5 before costs. Once per H1 bar, a favorable move of Trail_ATR_Mult times current completed ATR permits a trail using the same distance. No reverse-cross close is implemented. Stops do not guarantee a fill price or maximum realized loss.
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=INVALID_HANDLE;
int handleSlowEMA=INVALID_HANDLE;
int handleADX=INVALID_HANDLE;
int handleATR=INVALID_HANDLE;
//--- Global state
datetime lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialisation |
//+------------------------------------------------------------------+
int OnInit()
{
if(FastEMA_Period<1 || SlowEMA_Period<=FastEMA_Period || ADX_Period<1 || ATR_Period<1 || ADX_Threshold<0 || ADX_Threshold>100 || SL_ATR_Mult<=0 || TP_ATR_Mult<=0 || Trail_ATR_Mult<=0 || RiskPercent<=0 || RiskPercent>100 || MagicNumber<=0) return INIT_PARAMETERS_INCORRECT;
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(ENUM_ORDER_TYPE side,double entry,double stop)
{
double budget=AccountInfoDouble(ACCOUNT_EQUITY)*RiskPercent/100.0;
double minimum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),maximum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP),loss=0;
if(budget<=0 || minimum<=0 || maximum<minimum || step<=0 || entry<=0 || stop<=0) return 0;
if(!OrderCalcProfit(side,_Symbol,minimum,entry,stop,loss) || loss>=0) return 0;
double raw=budget*minimum/MathAbs(loss);
double lots=NormalizeDouble(MathFloor(MathMin(raw,maximum)/step)*step,8);
return lots>=minimum?lots:0;
}
//+------------------------------------------------------------------+
//| Helper: send a market order |
//+------------------------------------------------------------------+
bool PlaceOrder(ENUM_ORDER_TYPE orderType, double sl, double tp, double lots)
{
if(lots<=0) { Print("No volume within estimated budget"); return false; }
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) || (result.retcode!=TRADE_RETCODE_DONE && result.retcode!=TRADE_RETCODE_DONE_PARTIAL))
{
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);
if(!OrderSend(req,res) || res.retcode!=TRADE_RETCODE_DONE) Print("Trail failed: ",res.retcode);
}
}
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);
if(!OrderSend(req,res) || res.retcode!=TRADE_RETCODE_DONE) Print("Trail failed: ",res.retcode);
}
}
}
}
//+------------------------------------------------------------------+
//| 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 == 0 || currentBarTime == lastBarTime) return;
//--- Static buffers: index 0 = shift 2, index 1 = completed shift 1
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];
if(atrNow<=0) return;
double closeBar[];
if(CopyClose(_Symbol,PERIOD_H1,1,1,closeBar)!=1) return;
lastBarTime=currentBarTime;
//--- 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 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 lots = CalcLotSize(ORDER_TYPE_BUY,ask,sl);
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 lots = CalcLotSize(ORDER_TYPE_SELL,bid,sl);
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 entry, exit, position, order, and risk rules 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 | Strategy orders are simulated; live automation needs a separate 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.
Frequently Asked Questions
Related MQL5 pages
Risk and testing note
These educational examples require independent compilation and testing. A compile pass or an illustrative setting does not establish execution correctness or profitability. Any reported performance must be supported by reproducible data and test settings; past results do not predict future returns. Trading involves significant risk of loss. This content is not financial advice.