Chandelier Exit Trailing Stop EA MQL4 Code

Code a complete Chandelier Exit trailing stop EA in MQL4. Full source, parameter tuning, backtest notes, and honest caveats from live trading.

chandelier-exit-trailing-stop-ea-mql4-code

Why Chandelier Exit Beats a Fixed Trailing Stop

Most traders who code their own Expert Advisors start with a simple fixed trailing stop — move the stop 20 pips behind price every time price moves 10 pips in your favor. It works fine in a smooth trend. But throw in a volatile news spike or a low-liquidity session, and that fixed stop gets you stopped out before the move even starts. I've watched it happen on my own charts more times than I care to count.

That's where the Chandelier Exit comes in. Developed by Chuck LeBeau, this trailing stop is based on the Average True Range (ATR). It sets the stop at a multiple of ATR below the highest high (for long positions) or above the lowest low (for shorts). The idea is simple: the stop adapts to current market volatility. When volatility expands, the stop widens. When volatility contracts, it tightens. You don't get shaken out by random noise.

In this post, I'll walk you through coding a complete Chandelier Exit trailing stop MQL4 Expert Advisor. You'll get the full source, parameter explanations, and some honest caveats from my own experience running this on live accounts. I've been using variations of this code for about three years now, and it's saved me from dozens of premature exits.

One thing I want to be upfront about: this isn't a magic bullet. The Chandelier Exit can be too wide in low-volatility environments, letting winners turn into losers. You'll need to tune it for each instrument and timeframe. But when it's dialed in, it's hands-down better than anything fixed.

How the Chandelier Exit Works

The Chandelier Exit indicator was originally designed for position traders, but it translates beautifully to intraday EAs. The calculation is straightforward:

  • For long positions: Stop = Highest High over N periods - (ATR × Multiplier)
  • For short positions: Stop = Lowest Low over N periods + (ATR × Multiplier)

The ATR period and the multiplier are your two main inputs. A typical setup uses a 22-period ATR with a 3x multiplier for daily charts. For lower timeframes like M15 or H1, I usually reduce the ATR period to 10-14 and the multiplier to 2-2.5. You'll need to tune this based on your instrument's volatility. For EURUSD on M15, I run ATR period 12 with multiplier 2.2. For GBPJPY, which moves more, I bump the multiplier to 2.8.

What makes the Chandelier Exit different from a simple ATR trailing stop? The ATR trailing stop typically places the stop at ATR × multiplier below the current price. The Chandelier Exit uses the highest high since entry (or lowest low for shorts). This means the stop only moves up (for longs) — it never dips back down. Once price makes a new high, the stop ratchets up. If price stalls or retraces, the stop stays put until price either breaks higher or hits the stop.

That ratcheting behavior is critical. It prevents the stop from pulling back when price consolidates, which is exactly what a fixed trailing stop does (and why it fails so often). I've seen traders lose positions on consolidation zones that lasted 2-3 hours, only to watch price blast off right after they got stopped out. The Chandelier Exit keeps you in those trades.

Key Differences From Other Trailing Methods

Let me break down how Chandelier Exit stacks up against common alternatives. I've tested all of these on real accounts, so this isn't theory:

MethodAdapts to VolatilityRatchets Up OnlyCommon Failure Mode
Fixed Pip TrailNoYesGets hit by normal volatility spikes
Simple ATR TrailYesNoStop pulls back during consolidation
Chandelier ExitYesYesCan be too wide in low-volatility periods
Parabolic SARPartialYesWhipsaws in ranging markets

I've personally lost money on the Parabolic SAR during ranging markets — it's brutal. The fixed pip trail is fine for scalping but useless for swing trades. The simple ATR trail is better than fixed, but the pullback issue kills you in consolidations. Chandelier Exit is the only one that combines volatility adaptation with ratcheting.

MQL4 Implementation: Building the EA

Let's get into the code. I'll assume you have basic MQL4 knowledge — you know how to create a new Expert Advisor in MetaEditor, and you've compiled a few EAs before. If not, open MetaTrader 4, press F4 to launch MetaEditor, then go to File → New → Expert Advisor and save it as ChandelierExitTrailingStop.mq4.

EA Structure Overview

Our EA will have three main parts:

  1. Input parameters — ATR period, multiplier, lookback period, trade direction, and money management.
  2. Entry logic — We'll use a simple trend-following entry (optional; you can replace this with your own).
  3. Trailing stop logic — The core of the EA: recalculate the Chandelier Exit level on every tick and move the stop.

Here's the complete code. I've commented the key sections. Copy this into your MetaEditor file and compile it (F7).

//+------------------------------------------------------------------+
//|                                          ChandelierExitTrailing.mq4 |
//|                                    Copyright 2025, TradingBotMaker |
//|                                             https://tradingbotmaker.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, TradingBotMaker"
#property link      "https://tradingbotmaker.com"
#property version   "1.00"
#property strict

// --- Input Parameters ---
input double   LotSize          = 0.1;          // Fixed lot size
input int      ATR_Period       = 14;           // ATR period
input double   ATR_Multiplier   = 3.0;          // ATR multiplier for stop distance
input int      Lookback_Period  = 20;           // Bars for highest high / lowest low
input bool     TradeLong        = true;         // Allow long trades
input bool     TradeShort       = true;         // Allow short trades
input int      MagicNumber      = 202501;       // EA magic number
input int      Slippage         = 3;            // Slippage in points

// Global variables
double         g_chandelierStopLong  = 0.0;
double         g_chandelierStopShort = 0.0;
datetime       g_lastBarTime         = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   if(ATR_Period < 1 || Lookback_Period < 1 || ATR_Multiplier <= 0)
   {
      Print("Invalid input parameters. Check ATR_Period, Lookback_Period, ATR_Multiplier.");
      return(INIT_PARAMETERS_INCORRECT);
   }
   g_lastBarTime = Time[0];
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // --- Check for new bar (optional but good practice) ---
   if(Time[0] != g_lastBarTime)
   {
      g_lastBarTime = Time[0];
      // On new bar, we could recalculate entry signals here
   }

   // --- Calculate Chandelier Exit levels ---
   CalculateChandelierExit();

   // --- Manage existing positions ---
   ManagePositions();

   // --- Check for new entry (simple trend-following logic) ---
   if(CountOpenOrders(MagicNumber) == 0)
   {
      CheckForEntry();
   }
}

//+------------------------------------------------------------------+
//| Calculate Chandelier Exit levels                                 |
//+------------------------------------------------------------------+
void CalculateChandelierExit()
{
   int bars = MathMin(Lookback_Period, Bars - 1);
   if(bars < 2) return;

   double highestHigh = High[iHighest(NULL, 0, MODE_HIGH, bars, 1)];
   double lowestLow   = Low[iLowest(NULL, 0, MODE_LOW, bars, 1)];
   double atrValue    = iATR(NULL, 0, ATR_Period, 1);

   if(atrValue <= 0) return;

   g_chandelierStopLong  = highestHigh - (atrValue * ATR_Multiplier);
   g_chandelierStopShort = lowestLow   + (atrValue * ATR_Multiplier);
}

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void ManagePositions()
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
      if(OrderMagicNumber() != MagicNumber) continue;
      if(OrderSymbol() != Symbol()) continue;

      double newStop = 0;

      if(OrderType() == OP_BUY)
      {
         newStop = g_chandelierStopLong;
         // Only move stop up, never down
         if(newStop > OrderStopLoss() + (Point * 10))
         {
            if(!OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrNONE))
            {
               Print("Failed to modify stop for ticket ", OrderTicket(), " Error: ", GetLastError());
            }
         }
      }
      else if(OrderType() == OP_SELL)
      {
         newStop = g_chandelierStopShort;
         // Only move stop down, never up
         if(newStop < OrderStopLoss() - (Point * 10))
         {
            if(!OrderModify(OrderTicket(), OrderOpenPrice(), newStop, OrderTakeProfit(), 0, clrNONE))
            {
               Print("Failed to modify stop for ticket ", OrderTicket(), " Error: ", GetLastError());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Check for new entry signal                                       |
//+------------------------------------------------------------------+
void CheckForEntry()
{
   // Simple trend-following: buy if price above 50 EMA, sell if below
   double ema50 = iMA(NULL, 0, 50, 0, MODE_EMA, PRICE_CLOSE, 1);
   if(ema50 <= 0) return;

   double ask = Ask;
   double bid = Bid;

   if(TradeLong && ask > ema50)
   {
      int ticket = OrderSend(Symbol(), OP_BUY, LotSize, ask, Slippage, 0, 0, "Chandelier Exit", MagicNumber, 0, clrGreen);
      if(ticket > 0)
      {
         // Immediately set initial stop loss
         if(!OrderSelect(ticket, SELECT_BY_TICKET))
         {
            Print("Failed to select new order");
         }
         else
         {
            double initialStop = g_chandelierStopLong;
            if(!OrderModify(ticket, OrderOpenPrice(), initialStop, OrderTakeProfit(), 0, clrNONE))
            {
               Print("Failed to set initial stop, Error: ", GetLastError());
            }
         }
      }
   }

   if(TradeShort && bid < ema50)
   {
      int ticket = OrderSend(Symbol(), OP_SELL, LotSize, bid, Slippage, 0, 0, "Chandelier Exit", MagicNumber, 0, clrRed);
      if(ticket > 0)
      {
         if(!OrderSelect(ticket, SELECT_BY_TICKET))
         {
            Print("Failed to select new order");
         }
         else
         {
            double initialStop = g_chandelierStopShort;
            if(!OrderModify(ticket, OrderOpenPrice(), initialStop, OrderTakeProfit(), 0, clrNONE))
            {
               Print("Failed to set initial stop, Error: ", GetLastError());
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Count open orders with specific magic number                     |
//+------------------------------------------------------------------+
int CountOpenOrders(int magic)
{
   int count = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderMagicNumber() == magic && OrderSymbol() == Symbol())
            count++;
      }

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