Parabolic SAR Trailing Stop EA in MQL5: Code & Strategy

Production-ready MQL5 Expert Advisor using Parabolic SAR for dynamic trailing stops. Full code, parameter explanations, backtest walkthrough on EURUSD H4, and.

parabolic-sar-trailing-stop-ea-in-mql5-code

Why Parabolic SAR for Trailing Stops?

Most traders I meet slap a fixed pip trailing stop on their EA and call it a day. That works fine until you hit a volatile spike — then you're stopped out at the worst possible moment while the trend resumes without you. The Parabolic SAR (Stop and Reverse) was designed by Welles Wilder to solve exactly this problem: it tightens the stop as price accelerates and loosens it during sideways shuffles. In MQL5, implementing this as a dynamic trailing stop for an existing trend-following EA is straightforward, but the devil's in the details — and most code I've seen online gets those details wrong.

This post walks through a production-ready MQL5 trailing stop EA that uses the Parabolic SAR to manage open positions. You'll get the full code, input parameter explanations, a backtest walkthrough on EURUSD H4, and an honest discussion of where this approach shines and where it'll burn you. If you're moving from MT4 to MT5 and need practical Parabolic SAR EA code, this is for you.

I've been coding EAs for about six years now, and I'll tell you straight: the Parabolic SAR is not a magic bullet. It works beautifully in strong, clean trends — think EURUSD daily in 2022 or GBPJPY hourly on a breakout day. But in choppy, range-bound markets, it'll chew through your stop adjustments and rack up slippage costs. The key is knowing when to use it and how to code it properly.

How the Parabolic SAR Trailing Stop Works

The Parabolic SAR plots dots above or below price. In an uptrend, dots sit below price and creep upward. In a downtrend, they sit above and creep downward. The indicator has two parameters: Step (acceleration factor, default 0.02) and Maximum (maximum acceleration, default 0.2). The SAR accelerates as the trend persists, pulling the stop closer to price.

For a trailing stop EA, the logic is simple:

  • For a long position: trail the stop loss at the current Parabolic SAR value, but only if the SAR is below the current stop. Never raise the stop above the current SAR — that invites premature exits.
  • For a short position: trail the stop loss at the current Parabolic SAR value, but only if the SAR is above the current stop. Never lower the stop below the SAR.
  • If the SAR flips sides (indicating a potential trend reversal), you have the option to exit immediately or let the existing stop ride. I prefer the latter — let the market confirm the flip.

The key insight most tutorials miss: you must check the SAR only on new bars. Checking on every tick causes the SAR to bounce around intra-bar, creating phantom stops that get triggered and then the market reverses. I've watched traders blow accounts this way. Always use OnTick() with a bar check.

Let me break down why this matters in practice. Say you're long EURUSD on the H1 chart. The SAR value at the start of the bar is 1.1050, and your current stop is at 1.1045. Mid-bar, price dips to 1.1048, and the SAR recalculates to 1.1047 on a tick. If you're updating stops every tick, you'd move your stop to 1.1047, then price bounces back to 1.1060. Next tick, SAR jumps to 1.1055 — you've already tightened too much. On a new bar, the SAR resets properly based on the full bar's range. That's the difference between a smooth trail and a choppy mess.

Understanding the Acceleration Factor

The Step parameter controls how quickly the SAR accelerates. At 0.02, the SAR increases by 2% of the current bar's range each time a new extreme is made. After 10 consecutive new highs, the acceleration reaches 0.2 (the Maximum). This means the stop tightens progressively as the trend extends. I've found that for slower timeframes like H4 or daily, keeping Step at 0.02 and Maximum at 0.2 works well. For M15 or M5, you might want Step at 0.04 and Maximum at 0.25 — the faster acceleration helps catch quick moves before they reverse.

One edge case that catches people: when the SAR flips from below to above price on a long, the indicator value jumps dramatically. If you're using ExitOnFlip = true, you need to handle this gracefully. The code I'll show you checks the flip condition against bid/ask, not just the SAR value alone. That avoids closing on a flip that hasn't truly crossed price.

MQL5 Implementation: The Full EA

Below is a complete Expert Advisor that applies a Parabolic SAR trailing stop to any open position. It assumes you already have an entry signal — this EA only manages exits. You can attach it to a chart with your entry EA running separately, or integrate the trailing logic into your existing EA's OnTick().

Let me walk you through the structure before the code.

EA Input Parameters

ParameterTypeDefaultDescription
SAR_Stepdouble0.02Parabolic SAR acceleration factor. Higher values tighten stops faster.
SAR_Maximumdouble0.2Maximum acceleration. Lower values (0.1) make stops looser in strong trends.
TrailOnlyOnNewBarbooltrueOnly update stop on new bar formation. Critical for stability.
ExitOnFlipboolfalseIf true, close position when SAR flips above/below price.
Slippageint3Slippage in points for stop modification orders.
MinStopDistanceint10Minimum distance in points between stop and current price (broker safety).
MagicNumberint0Filter by magic number. 0 = apply to all positions on chart.

Notice I added a Magic Number parameter in this version. In the earlier draft, I left it out — but if you run multiple EAs, you absolutely need to filter. I'll show you how that integrates into the code below.

The Core Code

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

input double   SAR_Step           = 0.02;
input double   SAR_Maximum        = 0.2;
input bool     TrailOnlyOnNewBar  = true;
input bool     ExitOnFlip         = false;
input int      Slippage           = 3;
input int      MinStopDistance    = 10;
input int      MagicNumber        = 0;

int      sarHandle;
datetime lastBarTime;

//+------------------------------------------------------------------+
int OnInit()
  {
   sarHandle = iSAR(_Symbol, PERIOD_CURRENT, SAR_Step, SAR_Maximum);
   if(sarHandle == INVALID_HANDLE)
     {
      Print("Failed to create SAR indicator handle. Error: ", GetLastError());
      return(INIT_FAILED);
     }
   lastBarTime = 0;
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
void OnTick()
  {
   // Bar check for stability
   if(TrailOnlyOnNewBar)
     {
      datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
      if(currentBarTime == lastBarTime)
         return;
      lastBarTime = currentBarTime;
     }

   double sarValues[1];
   if(CopyBuffer(sarHandle, 0, 0, 1, sarValues) != 1)
     {
      Print("Failed to copy SAR buffer. Error: ", GetLastError());
      return;
     }
   double sar = sarValues[0];

   // Get current price and point value
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double minDist = MinStopDistance * point;

   // Iterate through all open positions
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!PositionSelectByTicket(PositionGetTicket(i)))
         continue;

      long posType = PositionGetInteger(POSITION_TYPE);
      double posSL = PositionGetDouble(POSITION_SL);
      double posTP = PositionGetDouble(POSITION_TP);
      double posOpen = PositionGetDouble(POSITION_PRICE_OPEN);
      long posMagic = PositionGetInteger(POSITION_MAGIC);
      ulong ticket = PositionGetTicket(i);

      // Magic number filter
      if(MagicNumber != 0 && posMagic != MagicNumber)
         continue;

      if(posType == POSITION_TYPE_BUY)
        {
         // For long: new stop is SAR, but must be above current SL and within limits
         double newSL = sar;
         // Never lower the stop
         if(newSL <= posSL)
            continue;
         // Must be below current price with minimum distance
         if(newSL >= bid - minDist)
            continue;
         // Must be above open price (we're in profit)
         if(newSL <= posOpen)
            continue;

         MqlTradeRequest request = {};
         MqlTradeResult result = {};
         request.action = TRADE_ACTION_SLTP;
         request.position = ticket;
         request.symbol = _Symbol;
         request.sl = newSL;
         request.tp = posTP;
         request.deviation = Slippage;

         if(!OrderSend(request, result))
            Print("Failed to modify SL for buy #", ticket, ". Error: ", result.retcode);
        }
      else if(posType == POSITION_TYPE_SELL)
        {
         // For short: new stop is SAR, but must be below current SL and within limits
         double newSL = sar;
         // Never raise the stop
         if(newSL >= posSL)
            continue;
         // Must be above current price with minimum distance
         if(newSL <= ask +

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