Elder-Ray Index EA MQL5: Build Trend Momentum Strategy

Code an MQL5 Expert Advisor using the Elder-Ray Index for trend and momentum confirmation with a dynamic stop-loss. Includes full indicator code, entry logic.

elder-ray-index-ea-mql5-build-trend-momentum

Why the Elder-Ray Index Belongs in Your EA Toolkit

Most traders who build EAs gravitate toward the usual suspects: RSI, MACD, Bollinger Bands. Nothing wrong with that — they work. But if you've spent any time in the Strategy Tester staring at equity curves that look like a dying heartbeat, you know the market has a way of punishing lazy indicator combos. You need something that confirms both direction and conviction.

That's where the Elder-Ray Index comes in. Developed by Dr. Alexander Elder, it breaks price action into two components: Bull Power and Bear Power. These measure how far buyers or sellers have pushed price relative to a smoothed average (typically a 13-period EMA). When both align with the trend, you get entries with higher probability. When they diverge, you get early warnings.

In this post, I'll walk you through building a complete MQL5 EA that uses the Elder-Ray Index for trend and momentum confirmation, plus a dynamic stop-loss that adapts to market volatility. No fluff, no fake promises — just code you can compile and test today.

Understanding the Elder-Ray Index

Before we write a single line of MQL5, let's make sure we're on the same page about what this indicator actually tells us. The Elder-Ray Index consists of three components:

  • EMA (13-period): The trend filter. Price above = uptrend, below = downtrend.
  • Bull Power: High price minus the 13 EMA. Positive values mean bulls are in control.
  • Bear Power: Low price minus the 13 EMA. Negative values mean bears are in control.

The logic is simple but powerful. In a strong uptrend, Bull Power should be positive and rising, while Bear Power stays negative but shallow (or turns positive briefly). When both Bull Power and Bear Power are positive, that's extreme bullishness — often a blow-off top. When both are negative, you're looking at capitulation.

For our EA, we'll use three conditions:

  1. Trend filter: Price above the 13 EMA for long trades, below for short trades.
  2. Momentum confirmation: Bull Power positive and rising (current > previous) for longs; Bear Power negative and falling for shorts.
  3. Dynamic stop-loss: Based on the current Average True Range (ATR) multiplied by a user-defined factor, placed below the recent swing low (longs) or above the recent swing high (shorts).

This gives us a system that doesn't just chase price — it waits for evidence that the move has conviction.

Building the MQL5 EA Step by Step

Prerequisites and Setup

You'll need MetaTrader 5 and the MetaEditor IDE. If you haven't already, create a new Expert Advisor: File → New → Expert Advisor. Name it ElderRayTrendEA. We'll write everything from scratch — no external dependencies beyond the built-in iMA, iATR, and custom indicator functions.

Indicator Input Parameters

Let's define the inputs a trader can tweak without touching code. Open the EA file and add this block at the top:

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input double   LotSize          = 0.1;          // Fixed lot size
input int      MAPeriod         = 13;           // EMA period for trend
input int      ATRPeriod        = 14;           // ATR period for dynamic SL
input double   ATRMultiplier    = 1.5;          // ATR multiplier for SL
input bool     UseTrailingStop  = true;         // Enable trailing stop
input int      MagicNumber      = 202410;       // EA magic number

These are straightforward. The ATRMultiplier controls how aggressive your stop is — 1.5 means the stop is placed 1.5 times the current ATR value away from entry. I've found 1.5 to 2.0 works well on H1, but you'll want to optimize this for your timeframe.

Calculating Bull Power and Bear Power

MQL5 doesn't have a built-in Elder-Ray Index function, so we'll compute it manually. In the OnTick() handler, we need the EMA value and the high/low prices for the current and previous bars:

//+------------------------------------------------------------------+
//| Get Bull Power and Bear Power values                             |
//+------------------------------------------------------------------+
double GetBullPower(int shift)
{
   double ema = iMA(_Symbol, _Period, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, shift);
   if(ema == 0) return 0;
   return iHigh(_Symbol, _Period, shift) - ema;
}

double GetBearPower(int shift)
{
   double ema = iMA(_Symbol, _Period, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, shift);
   if(ema == 0) return 0;
   return iLow(_Symbol, _Period, shift) - ema;
}

Notice I'm using iHigh and iLow for the current bar, not the close. This is critical — Bull Power measures the maximum strength of buyers during the bar, not where the bar ended. Using close would give you a lagging signal.

Entry Logic

Now the core decision logic. We'll check conditions on every tick, but only act on new bars to avoid repainting:

//+------------------------------------------------------------------+
//| Check for new bar                                                |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, _Period, 0);
   if(currentBarTime != lastBarTime)
   {
      lastBarTime = currentBarTime;
      return true;
   }
   return false;
}

Inside OnTick(), we call IsNewBar() first. If it's a new bar, we evaluate entries:

void OnTick()
{
   if(!IsNewBar()) return;
   
   double ema = iMA(_Symbol, _Period, MAPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
   double close = iClose(_Symbol, _Period, 1);
   double bullPower = GetBullPower(1);
   double bullPowerPrev = GetBullPower(2);
   double bearPower = GetBearPower(1);
   double bearPowerPrev = GetBearPower(2);
   
   // Long entry conditions
   if(close > ema && bullPower > 0 && bullPower > bullPowerPrev && bearPower < 0)
   {
      if(!PositionExists(MagicNumber, POSITION_TYPE_BUY))
         OpenBuy();
   }
   
   // Short entry conditions
   if(close < ema && bearPower < 0 && bearPower < bearPowerPrev && bullPower > 0)
   {
      if(!PositionExists(MagicNumber, POSITION_TYPE_SELL))
         OpenSell();
   }
}

Let's break down the long condition: price above EMA (trend up), Bull Power positive and rising (bulls gaining strength), Bear Power negative (bears still present but not dominant). The short condition is the mirror image. Notice I'm using PositionExists() — you'll need to write that helper to check for open positions by magic number.

Dynamic Stop-Loss Calculation

This is where the EA gets interesting. Instead of a fixed pip stop, we calculate the stop based on ATR and the recent swing structure:

double CalculateStopLoss(double entryPrice, ENUM_ORDER_TYPE orderType)
{
   double atr = iATR(_Symbol, _Period, ATRPeriod, 1);
   double slDistance = atr * ATRMultiplier;
   
   if(orderType == ORDER_TYPE_BUY)
   {
      // Find the lowest low of the last 5 bars
      double lowestLow = iLow(_Symbol, _Period, iLowest(_Symbol, _Period, MODE_LOW, 5, 1));
      double sl = MathMin(entryPrice - slDistance, lowestLow - _Point * 10);
      return sl;
   }
   else // Sell
   {
      double highestHigh = iHigh(_Symbol, _Period, iHighest(_Symbol, _Period, MODE_HIGH, 5, 1));
      double sl = MathMax(entryPrice + slDistance, highestHigh + _Point * 10);
      return sl;
   }
}

The logic: we take the larger of (ATR-based distance) or (recent swing extreme + a small buffer). This ensures the stop is wide enough to survive normal volatility but tight enough to cap losses if the trend fails. I use 5 bars for the swing lookback — you can make this an input parameter.

Order Placement and Trailing

Here's the OpenBuy() function:

void OpenBuy()
{
   MqlTradeRequest request = {};
   MqlTradeResult result = {};
   
   double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double sl = CalculateStopLoss(entry, ORDER_TYPE_BUY);
   double tp = 0; // No fixed take profit — we'll trail
   
   request.action = TRADE_ACTION_DEAL;
   request.symbol = _Symbol;
   request.volume = LotSize;
   request.price = entry;
   request.sl = sl;
   request.tp = tp;
   request.deviation = 10;
   request.magic = MagicNumber;
   request.type = ORDER_TYPE_BUY;
   
   OrderSend(request, result);
}

For trailing, I prefer a simple approach: on each new bar, if the price has moved in our favor by at least 1 ATR, we move the stop to break even + a small buffer. If it moves further, we tighten the stop using a fixed ATR offset. Here's a minimal trailing implementation:

void TrailStop(ulong ticket)
{
   PositionSelectByTicket(ticket);
   double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   double currentSL = PositionGetDouble(POSITION_SL);
   double atr = iATR(_Symbol, _Period, ATRPeriod, 1);
   
   if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
   {
      double newSL = openPrice + atr * 0.5; // Move to break even + half ATR
      double distanceFromEntry = SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice;
      if(distanceFromEntry > atr * 1.5 && currentSL < newSL)
      {
         ModifyPosition(ticket, newSL, 0);
      }
   }
}

I call TrailStop() on every new bar for each open position. You can make the multiplier an input — I've used 0.5 here to give price some room after break-even.

Full Code Structure and Compilation Notes

Here's the skeleton of the EA. You'll need to fill in the helper functions (PositionExists, ModifyPosition) — they're standard MQL5 boilerplate:

//+------------------------------------------------------------------+
//| ElderRayTrendEA.mq5                                              |
//| Trend momentum with dynamic stop-loss                            |
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property version   "1.00"
#property description "Elder-Ray Index EA with dynamic ATR stop"

input double   LotSize          = 0.1;
input int      MAPeriod         = 13;
input int      ATRPeriod        = 14;
input double   ATRMultiplier    = 1.5;
input bool     UseTrailingStop  = true;
input int      MagicNumber      = 202410;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Your logic here
}

//+------------------------------------------------------------------+
//| Helper functions                                                 |
//+------------------------------------------------------------------+
double GetBullPower(int shift) { /* as above */ }
double GetBearPower(int shift) { /* as above */ }
bool IsNewBar() { /* as above */ }
double CalculateStopLoss(double entryPrice, ENUM_ORDER_TYPE orderType) { /* as above */ }
void OpenBuy() { /* as above */ }
void OpenSell() { /* as above */ }
void TrailStop(ulong ticket) { /* as above */ }
bool PositionExists(int magic, ENUM_POSITION_TYPE type) { /* iterate positions */ }
void ModifyPosition(ulong ticket, double sl, double tp) { /* use OrderSend with MODIFY */ }

Compilation note: If you get error "function returns reference to local variable", check your CalculateStopLoss — make sure you're returning a double, not a reference. Also, MQL5 requires explicit #property declarations for inputs; don't skip them.

Pros, Cons, and Risks

Let's be honest about what this EA can and cannot do.

<td style="border:1px solid #c8d8e4;padding:7px 10px;font-size:12px;vertical-align
AspectStrengthWeakness
Trend FilterEMA provides clear directional bias, reducing whipsawsLagging in fast markets — you'll miss the first 2-3 bars of a move
Momentum ConfirmationBull/Bear Power divergence catches trend exhaustion earlyCan give false signals in choppy sideways markets
Dynamic Stop-LossATR-based stops adapt to volatility, reducing random stop-outsWider stops in high volatility can lead to larger losses per trade

Want to build your own version?

Recreate similar entry logic, risk rules, and filters in TradingBotMaker—no MQL coding. Start free.

Community

Clap for the article and open comments only when you want to read them.

0 claps0 comments

Related articles