Complete .mq5 source code

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

AspectPine Script (TradingView)MQL5 (MetaTrader 5)
ExecutionSeries evaluated on chart updatesEvent handlers with explicit buffers
DeploymentRuns in TradingView with alertsRuns in the MT5 terminal or on a VPS
Broker accessStrategy orders are simulated; live automation needs a separate integrationDirect broker connectivity
BacktestingStrategy Tester for strategy scriptsStrategy Tester for Expert Advisors
Code complexitySimpler, functional syntaxC++-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.