Markets are never flat.

They bounce around higher and lower every day, frequently punctuated by momentum moves where they continue higher and higher or lower and lower for a few days in a row before they seem to hit a wall and bounce the other direction. This medium-term momentum is what the Parabolic Stop-and-Reverse (PSAR or just SAR) indicator is designed to detect.

The Stop-and-Reverse portion refers to the way the trend will immediately stop when the trend ends and reverse by taking changing from long to short and vice versa.

The SAR was devised by famed trader Welles Wilder in his classic 1978 book on trading systems. He came up with it to help manage risk to use as a guide for trailing stop losses so he knew when to get out of positions. Over the years, it has evolved and is also frequently used as an entry/exit signal on its own, or in conjunction with other indicators.

As Wilder himself said about the SAR, he knows of no better indicator to squeeze profits out of these moves.

The Big Idea

The SAR uses the highs and lows of a price to gauge the strength of the trend. If the price moves keep hitting higher highs, then we have a strong upward trend.

The highest high (or lowest low) of a trend is called the extreme point (EP) and is an important figure in the SAR calculation.

If you have an upward trending price series that has highs of 10, 12, 15, 13, 14, then the extreme point would be 15.

We also have something called the acceleration factor (AF) which determines how much weight we give to new extreme points when we update the SAR at each time step. This is usually set at 0.02 as an initial value, then we increase it by the acceleration factor step every time we get a new extreme point. The acceleration factor usually has a maximum value of 0.2. Of course, these values aren't set in stone and can be changed as you see fit.

For an upward trending price series, the SAR is going to sit at or below the low values and is usually represented by dots below the price line. If the series is trending downwards, then the dots are above the price trend.

psar-msft-example.png

the PSAR is a predictive indicator meaning we're calculating for future periods. On day t, we're calculating it for day t+1, then we go back and get the new values when they're available so we can calculate it again for the next day.

The primary complication all of this adds is that we need to be careful about our time indices when we do code this up.

For days with an up trend and a new extreme point (e.g. the current day's high is greater than the existing extreme point) we can calculate the future SAR as:

$$SAR_{t+1} = SAR_t + AF_t \big(EP_t - SAR_t\big)$$

If we're on a down trend and we have a slightly different equation:

$$SAR_{t+1} = SAR_t - AF_t \big(SAR_t - EP_t\big)$$

Additionally, in both cases where we make a new extreme point, we'll increment the acceleration factor by 0.02 up until the maximum:

$$AF_{t+1} = \textrm{max}\big(AF_t + \delta AF, AF^{max} \big)$$

How to Calculate the Parabolic SAR

The SAR isn't that hard to calculate, but it is somewhat complicated. Most guides just show the simple update equations we provided above and wave their hands at the rest.

Well, you won't find that here! We didn't hold back on the gory details.

I'm overstating it, it's really not that bad. Check out the flow chart below to get an overview of the procedure.

psar-revised-diagram.jpg

The SAR has a lot of branching based on the direction of the trend and then rules about what to do depending on its strength, which will impact how the parameters get updated.

We're going to walk through the algorithm first and lay out each step, then get to some examples and the code before showing you how to trade it.

Let's jump in!

Initializing the Parabolic SAR

If you go back to Wilder's original book, he doesn't ever tell you how to initialize the SAR, he just begins from Day 4 with some starting values and goes from there. Very helpful.

Googling around, there are a few solutions out there. Some people just say we're starting in an uptrend, others initialize it randomly, and others try to get it out of the first few data points. We're going to take this last approach to initialization with a very simple rule.

  • If today's high is greater than yesterday's high, we're in an up trend. The initial SAR value is set to the lowest low of the past two days and the initial extreme point is set to the highest high of the past two days.
  • If today's high is not greater than yesterday's high, we're in a down trend. The initial SAR value is set to the highest high of the past two days and the initial extreme point is set to the lowest low of the past two days.

You need two days of data to get started on this, then you can get the SAR for the third day. If we want to make it mathy, then we can write our initialization rule like this:

$$SAR_{t+1} = \left\{ \begin{array}{11} \textrm{min} \big(Lows \big) \; \textrm{if } H_{t} > H_{t-1} \\ \textrm{max} \big(Highs \big) \; \textrm{otherwise} \end{array} \right.$$

With our indicator initialized, we'll turn to the procedure for updating it during an up trend.

How to Update a Bullish Parabolic SAR

  1. If the trend is up, calculate tomorrow's SAR value as:
$$ SAR_{t+1} = SAR_t + AF_t (EP_t - SAR_t)$$

2. Ensure the latest SAR value is as low or lower than the low price of the past two days:

$$SAR_{t+1} = \textrm{min} \big(Low_{t-1}, Low_{t}, SAR_{t+1})$$

Assuming you're doing all of the at the end of day t, you don't have to wait for markets to close to get the day's high and low. If that's the case, we'll roll right along and check for reversals, update the acceleration factor, and get the latest extreme point. Again, this is all assuming we're in an up-trend.

3. Add the latest high to the current trend list (this is just a list of all the highs that we hit during the trend, you'll see this in action in a bit).

4. Check for a trend reversal. If SAR_{t+1} is greater than Low_t, we have a trend reversal and we'll set SAR_{t+1} to the highest value from our trend list.

$$SAR_{t+1} = \left\{ \begin{array}{11} \textrm{max} \big(Trend^{highs} \big) \; \textrm{if } SAR_{t+1} > Low_{t+1} \\ SAR_{t+1} \; \textrm{otherwise} \end{array} \right.$$

5. If a reversal did not occur and if the new high is greater than the extreme point, update the extreme point and the acceleration factor. Otherwise, keep them the same. If a reversal did occur in the last step, then set the extreme point to the current low and reset the acceleration factor back to the initial value.

$$EP_{t+1} = \left\{ \begin{array}{11} Low_{t+1} \; \textrm{if } Reversal \\ \textrm{max} \big(High_{t+1}, EP_{t} \big) \; \textrm{otherwise} \end{array} \right.$$ $$AF_{t+1} = \left\{ \begin{array}{11} AF^{init} \; \textrm{if } Reversal \\ \textrm{min}\big(AF_{t} + \delta AF, AF^{max} \big) \; \textrm{otherwise} \end{array} \right.$$

How to Update a Bearish Parabolic SAR

If we're dealing with a down trend, then we go through the same procedure, just swapping out highs for lows and subtracting our SAR values to go lower. To remove any ambiguity, we'll write out all of the steps:

1. If the trend is down, calculate tomorrow's SAR value as:

$$ SAR_{t+1} = SAR_t - AF_t (SAR_t - EP_t)$$

2. Ensure the latest SAR value is as high or higher than the high price of the past two days:

$$SAR_{t+1} = \textrm{max} \big(High_{t-1}, High_{t}, SAR_{t+1})$$

If we're in a down trend, then it looks like this:
3. Add the latest low to the current trend list.
4. Check for a trend reversal. If SAR_{t+1} is lower than High_t, we have a trend reversal and we'll set SAR_{t+1} to the lowest value from our trend list.

$$SAR_{t+1} = \left\{ \begin{array}{11} \textrm{min} \big(Trend^{lows} \big) \; \textrm{if } SAR_{t+1} < High_{t+1} \\ SAR_{t+1} \; \textrm{otherwise} \end{array} \right.$$

5. If a reversal did not occur and if the new low is less than the extreme point, update the extreme point and the acceleration factor. Otherwise, keep them the same. If a reversal did occur in the last step, then set the extreme point to the current high and reset the acceleration factor back to the initial value.

$$EP_{t+1} = \left\{ \begin{array}{11} Low_{t+1} \; \textrm{if } Reversal \\ \textrm{max} \big(High_{t+1}, EP_{t} \big) \; \textrm{otherwise} \end{array} \right.$$ $$AF_{t+1} = \left\{ \begin{array}{11} AF^{init} \; \textrm{if } Reversal \\ \textrm{min}\big(AF_{t} + \delta AF, AF^{max} \big) \; \textrm{otherwise} \end{array} \right.$$

Step-By-Step Example for the Parabolic SAR

The above may be a bit hard to follow if you're new to this, but if you feel you've got a good grasp of the algorithm, jump down to the Python implementation. Otherwise we'll walk through a step-by-step example showing how this could be done by hand. This is not something you'd want to be doing manually (unless you're a masochist).

I find slowing down like this a great way to get started and understand precisely how an indicator functions so I have confidence in it and can develop new trading ideas.

We'll use Python and the yfinance package to get one month of data from Amazon to work with.

import pandas as pd
import yfinance as yf

ticker = 'AMZN'
yfObj = yf.Ticker(ticker)
data = yfObj.history(start='2021-08-01', 
                     end='2021-09-01')[['High', 'Low']].round(2)
data.reset_index(inplace=True)
data
psar-amazon-table-1a.png

All we need are the high and low values for the SAR, so we can throw everything else away to keep it tidier. I also changed the index from dates to number so we can write 𝑆𝐴𝑅_20 instead of 𝑆𝐴𝑅_2021−08−30 for ease of reference.

In his original work, Wilder doesn't show how to initialize the SAR, he just begins from Day 4 with a starting value. So we need to come up with an initialization rule. As shown above, we're going to look to see if today's high is greater than yesterday's, if so, then we'll label this an uptrend, otherwise it's a down trend.

In our case, we've got an uptrend (3,391>3,358), so our trend is set to 1 (0 will be downtrend) and our initial SAR value is the lowest low from the past two days:

$$SAR_2 = \textrm{min}\big(3317.00, 3299.77 \big) = 3,299.77$$

Likewise, our extreme point is going to be the highest high from the first two days:

$$EP_2 = \textrm{max} \big(3391.00, 3358.92 \big) = 3,391.00$$

Finally, our acceleration factor is set to our initial value of 0.02.

$$AF_2 = AF^{init} = 0.02$$

If we had a spreadsheet going, our sheet would look like this at the end of Day 1 (remember, we're 0-indexed to follow Python's convention):

psar-amazon-table-2.png

We can calculate this any time after close on Day 1 (August 3rd) once we have the high and low for that day. We'll update it once we have the high and low values on Day 2 (August 4th).

Fast forward one day, and the results come in, giving us these results:

psar-amazon-table-3.png

Now, we can calculate our SAR and all of the related values for Day 3.

We're past the initialization stage, so we check to see what our current trend reading is (1=up) and then we use our SAR formula to get the value for the next day.

$$SAR^{\prime}_3 = SAR_2 + AF_2 (EP_2 - SAR_2) = 3,299.77 + 0.02(3,391 - 3,299.77)$$ $$\Rightarrow SAR^{\prime}_3 = 3,301.59$$

Before we finalize our new SAR value, we need to compare it to the low from the past two days and take the lowest value of the three:

$$SAR_3 = \textrm{min}\big(SAR_3^{\prime}, Low_2, Low_1 \big) = \textrm{min}\big(3301.59, 3345.56, 3299.77\big)$$ $$\Rightarrow SAR_3 = 3,299.77$$

In this case, our previous low was lower than our SAR value, so we re-define SAR down to match it.

psar-amazon-table-4.png

Again, we need to wait until the end of Day 3 (August 5th) so we can finish updating our results and calculate the SAR for Day 4. Getting the next high and low values gives us:

psar-amazon-table-5.png

With the new high and low, we need to check to see if the low dropped below our current SAR value. If so, then we have a trend reversal. That didn't happen here (3,340.92>3,299.77) so we continue with our upward trend and need to get our new extreme point and acceleration factor.

If the new high was greater than the extreme point, then we'd update it and the acceleration factor. But, the new high is just below the extreme point so these values remain unchanged.

psar-amazon-table-6.png

Finally, we'll calculate the SAR for Day 4.

$$SAR_4^{\prime} = SAR_3 + AF \big(EP_3 - SAR_3 \big) = 3,299.77 + 0.02 \big(3,391 - 3,299.77 \big)$$ $$\Rightarrow SAR_4^{\prime} = 3,301.59$$

Comparing with the lows from the previous two periods we get:

$$SAR_4 = \textrm{min}\big(SAR_4^{\prime}, Low_3, Low_2 \big) = \textrm{min}\big(3301.59, 3340.29, 3345.56\big)$$ $$\Rightarrow SAR_4 = 3,301.59$$
psar-amazon-table-7.png

We could keep going like this, but instead, let's jump ahead a few days to Day 7 (August 10th) and see what a reversal looks like. Below is the table with our Day 7 SAR.

psar-amazon-table-8.png

We don't know it yet, but a reversal is coming up!

At the end of the day, we plug our high and low values into the table and check the results.

psar-amazon-table-9.png

The low finishes well below the SAR which is going to trigger our reversal. To fill in our values, we switch trend from 1 to 0 (down trend) and reset our acceleration factor back to 0.02 (in our case, it never budged, but that's OK). The extreme point just gets set to our current low.

The next day's SAR becomes the highest value from the previous trend. In the table, we just take the maximum from the High column as long as Trend = 1, which gives us 3,389.

Filling in all of these values, we now get:

psar-amazon-table-10.png

Now that we have the down trend in place, we'd just follow the same procedure, but we'd be changing some signs and swapping highs for lows until the next reversal. And on and on it would go from long to short and short to long.

The whole procedure for calculating the Parabolic SAR is a bit involved and would be a nightmare to do by hand (or in Excel for that matter - but maybe that's just my anti-Excel bias). Let's turn to Python for getting this model in place.

Python Code for Calculating the Parabolic SAR

There's nothing difficult about any of the above calculations. You're just taking some min/max values and updating SAR and AF at each time step. Despite that, the entire algorithm is a bit complicated because of all of the if/else statements.

We have a class called PSAR that allows you to adjust the initialization for the acceleration factor, its step size, and the maximum acceleration factor to calculate the Parabolic SAR. Despite its complexity, it really only has these three parameters to play with, which can greatly improve the performance if done correctly.

from collections import deque

class PSAR:

  def __init__(self, init_af=0.02, max_af=0.2, af_step=0.02):
    self.max_af = max_af
    self.init_af = init_af
    self.af = init_af
    self.af_step = af_step
    self.extreme_point = None
    self.high_price_trend = []
    self.low_price_trend = []
    self.high_price_window = deque(maxlen=2)
    self.low_price_window = deque(maxlen=2)

    # Lists to track results
    self.psar_list = []
    self.af_list = []
    self.ep_list = []
    self.high_list = []
    self.low_list = []
    self.trend_list = []
    self._num_days = 0

  def calcPSAR(self, high, low):
    if self._num_days >= 3:
      psar = self._calcPSAR()
    else:
      psar = self._initPSARVals(high, low)

    psar = self._updateCurrentVals(psar, high, low)
    self._num_days += 1

    return psar

  def _initPSARVals(self, high, low):
    if len(self.low_price_window) <= 1:
      self.trend = None
      self.extreme_point = high
      return None

    if self.high_price_window[0] < self.high_price_window[1]:
      self.trend = 1
      psar = min(self.low_price_window)
      self.extreme_point = max(self.high_price_window)
    else: 
      self.trend = 0
      psar = max(self.high_price_window)
      self.extreme_point = min(self.low_price_window)

    return psar

  def _calcPSAR(self):
    prev_psar = self.psar_list[-1]
    if self.trend == 1: # Up
      psar = prev_psar + self.af * (self.extreme_point - prev_psar)
      psar = min(psar, min(self.low_price_window))
    else:
      psar = prev_psar - self.af * (prev_psar - self.extreme_point)
      psar = max(psar, max(self.high_price_window))

    return psar

  def _updateCurrentVals(self, psar, high, low):
    if self.trend == 1:
      self.high_price_trend.append(high)
    elif self.trend == 0:
      self.low_price_trend.append(low)

    psar = self._trendReversal(psar, high, low)

    self.psar_list.append(psar)
    self.af_list.append(self.af)
    self.ep_list.append(self.extreme_point)
    self.high_list.append(high)
    self.low_list.append(low)
    self.high_price_window.append(high)
    self.low_price_window.append(low)
    self.trend_list.append(self.trend)

    return psar

  def _trendReversal(self, psar, high, low):
    # Checks for reversals
    reversal = False
    if self.trend == 1 and psar > low:
      self.trend = 0
      psar = max(self.high_price_trend)
      self.extreme_point = low
      reversal = True
    elif self.trend == 0 and psar < high:
      self.trend = 1
      psar = min(self.low_price_trend)
      self.extreme_point = high
      reversal = True

    if reversal:
      self.af = self.init_af
      self.high_price_trend.clear()
      self.low_price_trend.clear()
    else:
        if high > self.extreme_point and self.trend == 1:
          self.af = min(self.af + self.af_step, self.max_af)
          self.extreme_point = high
        elif low < self.extreme_point and self.trend == 0:
          self.af = min(self.af + self.af_step, self.max_af)
          self.extreme_point = low

    return psar

The main method is the calcPSAR() method which takes new highs and lows and returns tomorrow's SAR value. This will initialize our SAR if it doesn't have enough data, otherwise it calls _calcPSAR() which executes all of the if/else statements according to the current trend and the most recent data on hand.

Once this is complete, we have an _updateCurrentVals() method that will take the new high/low values and check for any reversals and make updates to our SAR if necessary.

Hopefully the code is easy enough to follow. Let's show how you can apply it to a stock by taking the last three months of Amazon's 2021 price.

ticker = 'AMZN'
yfObj = yf.Ticker(ticker)
data = yfObj.history(start='2021-09-01', end='2022-01-01')
indic = PSAR()

data['PSAR'] = data.apply(
    lambda x: indic.calcPSAR(x['High'], x['Low']), axis=1)
# Add supporting data
data['EP'] = indic.ep_list
data['Trend'] = indic.trend_list
data['AF'] = indic.af_list
data.head()
psar-amazon-table-11.png

Once we initialize our indicator class, we can use Pandas' apply method to calculate it for each time step. Note that we only have PSAR values through December 31st in the data frame. To get the next days' prediction, we just need to call _calcPSAR() from our indicator, and then we can use it for our prediction.

indic._calcPSAR()

3331.169921875

Let's see how the plot looks:

colors = plt.rcParams['axes.prop_cycle'].by_key()['color']

psar_bull = data.loc[data['Trend']==1]['PSAR']
psar_bear = data.loc[data['Trend']==0]['PSAR']

plt.figure(figsize=(12, 8))
plt.plot(data['Close'], label='Close', linewidth=1)
plt.scatter(psar_bull.index, psar_bull, color=colors[1], label='Up Trend')
plt.scatter(psar_bear.index, psar_bear, color=colors[3], label='Down Trend')
plt.xlabel('Date')
plt.ylabel('Price ($)')
plt.title(f'{ticker} Price and Parabolic SAR')
plt.legend()
plt.show()
psar-amazon-plot1.png

We can see that the PSAR does a good job leading the price up and down, although it gets reversed on some choppy moves.

If you're with us this far you may be confident in calculating this indicator, but we haven't talked about how to trade it yet!

Trading with the Parabolic SAR

The first and most obvious strategy is to buy when the Parabolic SAR is bullish and calling for an uptrend, then go short when it flips for a down trend.

buy_sigs = data.loc[data['Trend'].diff()==1]['Close']
short_sigs = data.loc[data['Trend'].diff()==-1]['Close']

plt.figure(figsize=(12, 8))
plt.plot(data['Close'], label='Close', linewidth=1, zorder=0)
plt.scatter(buy_sigs.index, buy_sigs, color=colors[2], 
            label='Buy', marker='^', s=100)
plt.scatter(short_sigs.index, short_sigs, color=colors[4], 
            label='Short', marker='v', s=100)
plt.scatter(psar_bull.index, psar_bull, color=colors[1], label='Up Trend')
plt.scatter(psar_bear.index, psar_bear, color=colors[3], label='Down Trend')
plt.xlabel('Date')
plt.ylabel('Price ($)')
plt.title(f'{ticker} Price and Parabolic SAR')
plt.legend()
plt.show()
psar-amazon-plot2.png

The up arrows indicate long entry points and the down arrows short entry points. After the first arrow, each also indicates an exit as you change your position from long to short or short to long.

No indicator is perfect, so you can see this one has a few hits and misses - most painfully the miss in late October going short to close out a profit before the price reverses and shoots upward.

Using the Parabolic SAR to set Stop Losses

The original intent of the Parabolic SAR was to manage risk and set stops. This requires the model to use a second indicator for entry signals. Frequently, the relative strength index (RSI) or the average directional index (ADX).

If we were to use the RSI, for example, we could combine them for a trend following strategy - as shown in the last two RSI backtests here - with the SAR providing our stops. In this case, we would enter the trade when the RSI crosses above or below the centerline and the SAR is in the same direction. The SAR value would then define the trailing stop to protect you as the trade moves. Both indicators would need to be on similar time frames for the best results, e.g. ensure that the RSI lookback period isn't too large and slow to react causing you to miss the trade.

You could always add further indicators, such as the RSI convergence/divergence with EMA for trend confirmation and have the SAR as the stop.

Or you could combine the SAR with something like the MACD to enter when both agree and exit as soon as one flips its position (or they both confirm an exit).

Really, the possibilities are endless. But that can be part of the fun of trading (after making money, of course).

Discovering new combinations, new algorithms, and strategies to allow you to develop an edge is exciting - but it's also time consuming. We're building a platform to speed that whole process up for you. We have hardened trading software that allows you to design your algorithm as you see fit, test it, and take it to the market all without you having to write a single line of code.

We have limited space for our beta test, so check out our free demo and join us as we democratize algorithmic trading!