Pandas: Calculate the Relative Strength Index (RSI) on a Stock

What is the Relative Strength Index?

The Relative Strength Index (RSI) on a stock is a technical indicator.

The relative strength index (RSI) is a momentum indicator used in technical analysis that measures the magnitude of recent price changes to evaluate overbought or oversold conditions in the price of a stock or other asset. 

https://www.investopedia.com/terms/r/rsi.asp

A technical indicator is a mathematical calculation based on past prices and volumes of a stock. The RSI has a value between 0 and 100. It is said to be overbought if above 70, and oversold if below 30.

Step 1: How to calculate the RSI

To be quite honest, I found the description on investopedia.org a bit confusing. Therefore I went for the Wikipedia description of it. It is done is a couple of steps, so let us do the same.

  1. If previous price is lower than current price, then set the values.
    • U = close_now – close_previous
    • D = 0
  2. While if the previous price is higher than current price, then set the values
    • U = 0
    • D = close_previous – close_now
  3. Calculate the Smoothed or modified moving average (SMMA) or the exponential moving average (EMA) of D and U. To be aligned with the Yahoo! Finance, I have chosen to use the (EMA).
  4. Calculate the relative strength (RS)
    • RS = EMA(U)/EMA(D)
  5. Then we end with the final calculation of the Relative Strength Index (RSI).
    • RSI = 100 – (100 / (1 + RSI))

Notice that the U are the price difference if positive otherwise 0, while D is the absolute value of the the price difference if negative.

Step 2: Get a stock and calculate the RSI

We will use the Pandas-datareader to get some time series data of a stock. If you are new to using Pandas-datareader we advice you to read this tutorial.

In this tutorial we will use Twitter as an examples, which has the TWTR ticker. It you want to do it on some other stock, then you can look up the ticker on Yahoo! Finance here.

Then below we have the following calculations.

import pandas_datareader as pdr
from datetime import datetime

ticker = pdr.get_data_yahoo("TWTR", datetime(2020, 1, 1))
delta = ticker['Close'].diff()
up = delta.clip(lower=0)
down = -1*delta.clip(upper=0)
ema_up = up.ewm(com=13, adjust=False).mean()
ema_down = down.ewm(com=13, adjust=False).mean()
rs = ema_up/ema_down
print(ticker)

To have a naming that is close to the definition and also aligned with Python, we use up for U and down for D.

This results in the following output.

                 High        Low       Open      Close    Volume  Adj Close
Date                                                                       
2020-01-02  32.500000  31.959999  32.310001  32.299999  10721100  32.299999
2020-01-03  32.099998  31.260000  31.709999  31.520000  14429500  31.520000
2020-01-06  31.709999  31.160000  31.230000  31.639999  12582500  31.639999
2020-01-07  32.700001  31.719999  31.799999  32.540001  13712900  32.540001
2020-01-08  33.400002  32.349998  32.349998  33.049999  14632400  33.049999
...               ...        ...        ...        ...       ...        ...
2021-10-08  64.339996  63.310001  64.250000  63.680000   8094900  63.680000
2021-10-11  63.509998  62.070000  62.990002  62.099998   9020400  62.099998
2021-10-12  62.799999  60.790001  61.680000  61.450001   9952100  61.450001
2021-10-13  62.740002  61.509998  61.959999  62.200001   9423500  62.200001
2021-10-14  63.779999  62.759998  63.009998  63.130001   3455733  63.130001

This tutorial was written 2020-08-18 (updated in 2021-10-14), and comparing with the RSI for twitter on Yahoo! Finance.

From Yahoo! Finance on Twitter with RSI

As you can see in the lower left corner, the RSI for the same ending day was 51.56 (it was measured in trading hours, so the end-of-day number is different), which fits the calculated value. Further checks reveal that they also fit the values of Yahoo.

Step 3: Visualize the RSI with the daily stock price

We will use the matplotlib library to visualize the RSI with the stock price. In this tutorial we will have two rows of graphs by using the subplots function. The function returns an array of axis (along with a figure, which we will not use).

The axis can be parsed to the Pandas DataFrame plot function.

import pandas_datareader as pdr
from datetime import datetime
import matplotlib.pyplot as plt

ticker = pdr.get_data_yahoo("TWTR", datetime(2020, 1, 1))
delta = ticker['Close'].diff()
up = delta.clip(lower=0)
down = -1*delta.clip(upper=0)
ema_up = up.ewm(com=13, adjust=False).mean()
ema_down = down.ewm(com=13, adjust=False).mean()
rs = ema_up/ema_down
ticker['RSI'] = 100 - (100/(1 + rs))
# Skip first 14 days to have real values
ticker = ticker.iloc[14:]
print(ticker)
fig, (ax1, ax2) = plt.subplots(2)
ax1.get_xaxis().set_visible(False)
fig.suptitle('Twitter')
ticker['Close'].plot(ax=ax1)
ax1.set_ylabel('Price ($)')
ticker['RSI'].plot(ax=ax2)
ax2.set_ylim(0,100)
ax2.axhline(30, color='r', linestyle='--')
ax2.axhline(70, color='r', linestyle='--')
ax2.set_ylabel('RSI')
plt.show()

Also, we we remove the x-axis of the first graph (ax1). Adjust the y-axis of the second graph (ax2). Also, we have set two horizontal lines to indicate overbought and oversold at 70 and 30, respectively. Notice, that Yahoo! Finance use 80 and 20 as indicators by default.

12% Investment Solution

Would you like to get 12% in return of your investments?

D. A. Carter promises and shows how his simple investment strategy will deliver that in the book The 12% Solution. The book shows how to test this statement by using backtesting.

Did Carter find a strategy that will consistently beat the market?

Actually, it is not that hard to use Python to validate his calculations. But we can do better than that. If you want to work smarter than traditional investors then continue to read here.

Learn Python

Learn Python A BEGINNERS GUIDE TO PYTHON

  • 70 pages to get you started on your journey to master Python.
  • How to install your setup with Anaconda.
  • Written description and introduction to all concepts.
  • Jupyter Notebooks prepared for 17 projects.

Python 101: A CRASH COURSE

  1. How to get started with this 8 hours Python 101: A CRASH COURSE.
  2. Best practices for learning Python.
  3. How to download the material to follow along and create projects.
  4. A chapter for each lesson with a descriptioncode snippets for easy reference, and links to a lesson video.

Expert Data Science Blueprint

Expert Data Science Blueprint

  • Master the Data Science Workflow for actionable data insights.
  • How to download the material to follow along and create projects.
  • A chapter to each lesson with a Description, Learning Objective, and link to the lesson video.

Machine Learning

Machine Learning – The Simple Path to Mastery

  • How to get started with Machine Learning.
  • How to download the material to follow along and make the projects.
  • One chapter for each lesson with a Description, Learning Objectives, and link to the lesson video.

27 thoughts on “Pandas: Calculate the Relative Strength Index (RSI) on a Stock”

  1. i think there is a basic typo / error in the RSI formula:

    instead of:
    RSI = 100 – (100 / (1 – RSI))
    it should be:
    RSI = 100 – (100 / (1 + RSI))

    Reply
  2. Thanks Rune, fantastic job! I’d like to develop a Strategic (backtesting) where we buy in 30 and sell when go down 70, but I’m not getting😔. Could you give me the key to get it?

    Reply
    • Hi Hugo,
      Great question.
      If I understand you want to create a “Signal” when to BUY, SELL, or KEEP.

      This can be done as follows:

      ticker['Signal'] = 'KEEP'
      ticker.loc[ticker['RSI'] > 70, 'Signal'] = 'SELL'
      ticker.loc[ticker['RSI'] < 30, 'Signal'] = 'BUY'

      Cheers,
      Rune

      Reply
  3. Hello
    I like your RSI calculations

    Python has changed some syntax since 8 months ago

    Is there a possibility that you can update the code?

    I need everything but the chart calculation
    How many rest charts can I do in one excel sheet. Thanks. harrycbailey@sbcglobal.net. Thx harry

    Reply
    • Hi Harry,
      I just went through all calculations with the newest pandas updates. I have also updated with the numbers from today.
      It should be working.
      If it does not work for your please provide the versions of Python, pandas, pandas_datareader and matplotlib.
      Cheers, Rune

      Reply
  4. Hello Rune. I am still with you working hard trying to figure it out
    my first mistake was not entering this code in Jupyter! (import pandas_datareader as pdr)
    I have made progress!!. able to move past getting access to yahoo finance
    Python version is 3.9
    now having difficulity in this area

    hpq [‘delta’] = delta = hpq [‘close’] .diff()
    hpq [‘up’] = up = delta. clip (lower=0)
    hpq [‘down’] = down = -1*delta.clip(upper=0)
    and

    Calculate the relative strength (RS)
    RS = EMA(U)/EMA(D)
    Then we end with the final calculation of the Relative Strength Index (RSI).
    RSI = 100 – (100 / (1 + RSI))

    I am i to assume the twitter example is the correct code?

    Like your tutorials !!!!!!!

    Reply
  5. First
    i need to know how you reload the data. You dont explain how to do this.

    Problem: my
    Date delta heading UP, DOWN, RSI are the same values
    (NaN) (NaN) (NaN) (NaN)
    0.050001 0.050001 -0.0 100.0
    0.160000 0.16000 -0.0 100.0
    0.219999 0.219999 -0.0 100.
    Second these are my steps i followed
    hpq[‘delta’] = delta = hpq[‘Close’].diff()
    hpq[‘up’] = up = delta.clip(lower=0)
    hpq[‘down’] = down = -1*delta.clip(upper=0)
    ema_up = up.ewm(com=13, adjust=False) .mean()
    ema_down = down.ewm(com=13, adjust=False) .mean()
    rs = ema_up/ema_down
    hpq [‘RSI’] = 100 – (100/(1 + rs))
    hpq. head()

    Thanks

    I know i made a stupid mistake

    Reply
  6. OK this is Embarrassing. when i thought I finally got it I had no errors. I tried again I have all of these errors. I also cut and pasted your code and I still had issues also i would like to insert
    ticker[‘Signal’] = ‘KEEP’
    ticker.loc[hpqr[‘RSI’] > 70, ‘Signal’] = ‘SELL’
    ticker.loc[hpq[‘RSI’] < 30, 'Signal'] = 'BUY'
    ————————————————————————-
    hpq = pdr.get_data_yahoo("hpq", dt.datetime(2018,1,2))
    hpq.index = hpq.index.date
    hpq.head()

    ERROR IS

    NameError Traceback (most recent call last)
    in
    —-> 1 hpq = pdr.get_data_yahoo(“hpq”, dt.datetime(2018,1,2))
    2 hpq.index = hpq.index.date
    3 hpq.head()

    NameError: name ‘pdr’ is not defined

    —————————————————–
    delta = hpq[‘Close’].diff()
    up = delta.clip(lower=0)
    down = -1*delta.clip(upper=0)

    ERROR IS

    NameError Traceback (most recent call last)
    in
    —-> 1 delta = hpq[‘Close’].diff()
    2 up = delta.clip(lower=0)
    3 down = -1*delta.clip(upper=0)

    NameError: name ‘hpq’ is not defined

    ————————————————————

    ema_up = up.ewm(com=13, adjust=False).mean()
    ema_down = down.ewm(com=13, adjust=False).mean()

    ERROR IS

    NameError Traceback (most recent call last)
    in
    —-> 1 ema_up = up.ewm(com=13, adjust=False).mean()
    2 ema_down = down.ewm(com=13, adjust=False).mean()

    NameError: name ‘up’ is not defined

    —————————————————————-

    rs = ema_up/ema_down
    hpq[‘RSI’] = 100 -(100/(1+rs))

    ERROR IS

    NameError Traceback (most recent call last)
    in
    —-> 1 rs = ema_up/ema_down
    2 hpq[‘RSI’] = 100 -(100/(1+rs))

    NameError: name ’ema_up’ is not defined

    —————————————————————

    # Skip first 14 days to have real values
    hpq = hpq.iloc[14:]
    hpq.head()

    ERROR IS

    NameError Traceback (most recent call last)
    in
    1 # Skip first 14 days to have real values
    —-> 2 hpq = hpq.iloc[14:]
    3 hpq.head()

    NameError: name ‘hpq’ is not defined
    —————————————————————
    # Create a Pandas Excel writer using XLsWriter
    excel_file = ‘output.xlsx’
    sheet_name = ‘hpq’
    writer = pd.ExcelWriter(excel_file, engine=’xlsxwriter’)
    hpq[14:].to_excel(writer, sheet_hpq_sheet_hpq)

    ERROR IS

    NameError Traceback (most recent call last)
    in
    2 excel_file = ‘output.xlsx’
    3 sheet_name = ‘hpq’
    —-> 4 writer = pd.ExcelWriter(excel_file, engine=’xlsxwriter’)
    5 hpq[14:].to_excel(writer, sheet_hpq_sheet_hpq)

    NameError: name ‘pd’ is not defined
    ——————————————————————————
    # Access the XlsWriter wookbook and worksheet objects from dataframe
    workbook = writer.book
    worksheet = writer.sheets[sheet_hpq]

    ERROR IS

    NameError Traceback (most recent call last)
    in
    1 # Access the XlsWriter wookbook and worksheet objects from dataframe
    —-> 2 workbook = writer.book
    3 worksheet = writer.sheets[sheet_hpq]

    NameError: name ‘writer’ is not defined
    ————————————————————

    i discovered thes errors when I tried to go to excel

    I am happy to dontate to your favorite charity
    thanks

    Reply
  7. StoP!! I believe i worked the proper code out. no errors I am happy and I learned a lot Thank you!!!

    I need to know where to insert this code please

    hpq[‘Signal’] = ‘KEEP’
    hpq.loc[ticker[‘RSI’] > 70, ‘Signal’] = ‘SELL’
    hpq.loc[ticker[‘RSI’] > If i decide to run another stock, will the different stocks be on the same spreadsheet in different worksheets?

    Thanks so much!!!

    Reply
    • Yes you want to make a signal line:

      hpq[‘Signal sell’] = ticker[‘RSI’] > 70

      This will create true when sell

      Similarly you can make one for buy.
      I think this is the easiest way to do it.

      Reply
  8. this is the last part of the code

    # Close and save the Excel file
    writer.save()

    I hit RUN and the excel chart did not open

    I had a excel page open with HPQ as the name

    Reply
    • If you want it to excel.
      Assuming you have it in hpq.

      hpq.to_excel(‘hpq.xlsx’)

      This should be the last thing you do.
      Then it writes an excel sheet hpq.xlsx in the current folder.

      Reply
  9. # Close and save the Excel file
    writer.save()
    another warning

    :\Users\Harry C Bailey\anaconda3\lib\site-packages\xlsxwriter\workbook.py:336: UserWarning: Calling close() on already closed file.
    warn(“Calling close() on already closed file.”)

    Reply
  10. Dear Rune. If I calculate the RSI using these two scripts, it doesn’t give me the same result as yours.

    Using the TA-lib Python wrapper:

    import talib as ta
    ta.RSI (df [‘close’], timeperiod = 13)

    Using the pandas-ta library:

    import pandas_ta as pta
    pta.rsi (df [‘close’], length = 13)

    What can be the cause? Thanks for your practical and pedagogical scripts.

    Reply
    • That’s a very good question. I have followed the formula from investopedia and it firs the figures from Yahoo! Finance.
      I would have to investigate it a bit further.

      Reply
  11. Hi, Rune. Thanks for the great work. I was just curious why for the RSI calculation you use n-1 (for n=14 days): ema_up = up.ewm(com=13, adjust=False).mean() but for your MACD calculation you use n (for n=12 days): exp1 = ticker.ewm(span=12, adjust=False).mean(). I thought they would both be the same since you use ewm.

    Reply
    • Hi SSG,
      Great questions.
      There are different ways to set the decay in the EWM (see docs: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ewm.html)
      In this one I use the com (decay in terms of center of mass). The comparison with Yahoo! Finance is done with 14 days (you can specify the value as you please), but to use the com you need to set it one less (see formula for com in docs).
      For the MACD I use the span, which works a bit different. Again, you can specify the values as you please – these are just common values.

      Reply

Leave a Comment