RSI analysis in Python: Technical implementation

The RSI or Relative Strength Index is one of the main momentum indicators I personally use when making decisions about an options or stock trade, not as a main indicator, but a good tool to confirm the current momentum of a stock. All tools I’ve found or used so far ( broker custom paid tools included ) gives this indicator visually, so you can check where the RSI is on a certain period or a certain stock over a graphic, however you don’t have access to the data directly, just to its visual representation over a chart. This is inconvenient in many ways if you plan to actively work with this data, specially if you have a long watchlist of stocks.

The Relative Strength Index (RSI) is a measurement used by traders to assess the price momentum of a stock or other security. The basic idea behind the RSI is to measure how quickly traders are bidding the price of the security up or down. The RSI plots this result on a scale of 0 to 100. Readings below 30 generally indicate that the stock is oversold, while readings above 70 indicate that it is overbought. Traders will often place this RSI chart below the price chart for the security, so they can compare its recent momentum against its market price.

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

What problem is this tool trying to solve?

By checking the RSI momentum indicator we can see if a stock is in the overbought or oversold territory, this tells us that the probability of bouncing back increases, and that momentum of going in the opposite direction increases as the RSI touches its maximum values. The main problem with existing tools is that while you can see this visually, you don’t have access to the numbers, so you cannot automate this unless you’re scrapping a website, which is finicky at best. You must go through all your watchlist of stocks and confirm visually where is in RSI values are at that moment, this is even more taxing if you’re both investing and trading, as the period to check varies its values.

With this script I’m pulling the data for all stocks on my list and then making the calculation on my own, skipping the final visual representation step, this way I can work directly with the data and for example set alerts whenever a stock reach a designated value or range.

As follows you’ll see the technical implementation of the whole thing and how has been coded, if you don’t care about this ( which is perfectly fine ) you can just get and use the script from GitHub here.

Installing and importing the modules

While I was writing this RSI implementation using the Interactive Brokers API I discovered that it may not be necessary, and we can just get the historic daily data from Yahoo Finance, so this is the approach I took at the moment:

import pandas_datareader as pdr
import datetime as dt
import sys

Initial data

We’ll use the pandas_datareader module and the get_data_yahoo() method to get our initial data source, this will gives us a starting point of data to work with, specially the closing prices of a given stock by the end of the day, which is what we’re looking for in order to build our RSI indicator.

ticker = pdr.get_data_yahoo("AMD", dt.datetime(2021,1,1), dt.datetime.now())

The output will be something like this:

#                  High        Low       Open      Close    Volume  Adj Close
# Date                                                                       
# 2020-12-31  92.300003  90.870003  92.099998  91.709999  24930700  91.709999
# 2021-01-04  96.059998  90.919998  92.110001  92.300003  51802600  92.300003
# 2021-01-05  93.209999  91.410004  92.099998  92.769997  34208000  92.769997
# 2021-01-06  92.279999  89.459999  91.620003  90.330002  51911700  90.330002
# 2021-01-07  95.510002  91.199997  91.330002  95.160004  42897200  95.160004
# ...               ...        ...        ...        ...       ...        ...
# 2021-06-11  82.330002  80.699997  81.610001  81.309998  24290800  81.309998
# 2021-06-14  81.550003  80.199997  81.510002  81.550003  27830200  81.550003
# 2021-06-15  81.680000  80.220001  81.589996  80.470001  26194300  80.470001
# 2021-06-16  81.449997  78.959999  80.750000  80.110001  29560900  80.110001
# 2021-06-17  85.370003  80.570000  80.769997  84.559998  77822823  84.559998

# [116 rows x 6 columns]

Filtering data

We do not need all that data for this tool, but just the date and closing price, or better said, we’re looking for the difference between sessions in closing price. We’ll get both just to be sure we’re checking the correct data:

delta = ticker['Close']

It will return something like:

# Date
# 2020-12-31    91.709999
# 2021-01-04    92.300003
# 2021-01-05    92.769997
# 2021-01-06    90.330002
# 2021-01-07    95.160004
#                 ...    
# 2021-06-11    81.309998
# 2021-06-14    81.550003
# 2021-06-15    80.470001
# 2021-06-16    80.110001
# 2021-06-17    84.559998
# Name: Close, Length: 116, dtype: float64

At this point we need to calculate [Closing_price(n-1) - Closing_price(n)] in order to get the difference between the closing prices of consecutive market sessions, for this we can just go ahead and use Panda’s .diff() method, so we don’t need to calculate these “manually”:

delta = ticker['Close'].diff()

The resulting column will be:

# Date
# 2020-12-31         NaN
# 2021-01-04    0.590004
# 2021-01-05    0.469994
# 2021-01-06   -2.439995
# 2021-01-07    4.830002
#                 ...   
# 2021-06-11   -0.250000
# 2021-06-14    0.240005
# 2021-06-15   -1.080002
# 2021-06-16   -0.360001
# 2021-06-17    4.449997
# Name: Close, Length: 116, dtype: float64

Note that the first value is NaN ( Not a Number ) because there’s no data for (n-1) we can use when calculating the difference between sessions, that’s perfectly fine. We also double check manually that other values are correctly just to be sure to be in the right track, for example:

If we take the data from delta = ticker['Close'] we can calculate the difference between the 1st and the 2nd value ( 2020-11-04 and 2020-12-31 ) , these are closing prices of 92.300003 - 91.709999 = 0.590004, so we’re correctly calculating the difference here via diff() . Later on we can add unit tests at this point to be sure that is not breaking on future iterations, but for now let’s move on:

Transforming initial data into the RSI indicator

At this point we need to transform the data we got into input to feed the RSI indicator. For this momentum indicator we basically need 2 inputs:

  • Up = Average of all up-moves in the last N price bars.
  • Down = Average of all down-moves in the last N price bars.
up = delta.clip(lower=0)
# Date
# 2020-12-31         NaN
# 2021-01-04    0.590004
# 2021-01-05    0.469994
# 2021-01-06    0.000000 ### Clipped from -2.43 to 0
# 2021-01-07    4.830002
#                 ...   
# 2021-06-11    0.000000 ### Clipped from -0.25 to 0
# 2021-06-14    0.240005
# 2021-06-15    0.000000 ### Clipped from -1.08 to 0
# 2021-06-16    0.000000 ### Clipped from -0.36 to 0
# 2021-06-17    4.449997
# Name: Close, Length: 116, dtype: float64
down = -1*delta.clip(upper=0)
# Date
# 2020-12-31         NaN
# 2021-01-04   -0.000000
# 2021-01-05   -0.000000
# 2021-01-06    2.439995
# 2021-01-07   -0.000000
#                 ...   
# 2021-06-11    0.250000
# 2021-06-14   -0.000000
# 2021-06-15    1.080002
# 2021-06-16    0.360001
# 2021-06-17   -0.000000
# Name: Close, Length: 116, dtype: float64

Creating the exponential weighted (EW) functions.

At this point we have all the data ready to feed our Relative Strength calculation, so we’ll dig into the meat and potatoes:

Pandas comes with a handy method called ewm(), this will provide us with exponential weighted calculations from the data we feed into it. This is important because we’re giving more importante, or weight, to recent price data. We’ll also be applying the mean() method which gives us the statistical mean (average) of a given DataFrame.

We’ll adjust the ewm() parameters to calculate this recurrently and with no decay, as there’s no imbalance in prices as could be in other uses of this function.

Once we get the Relative Strength calculation we only need to convert this into an index by calculating its value over 0-100, and then we add this Relative Strength Index values as a new column to our initial Dataframe:

	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) )
	print(ticker)

This will be the output, notice the last column showing the RSI for a given day. You can also change the last print statement for print(ticker['RSI']) in order to get only this column and ignore the rest ( which is what I’m doing ).

# Example output
#                  High        Low       Open  ...    Volume  Adj Close        RSI
# Date                                         ...                                
# 2020-01-02  32.500000  31.959999  32.310001  ...  10721100  32.299999        NaN
# 2020-01-03  32.099998  31.260000  31.709999  ...  14429500  31.520000   0.000000
# 2020-01-06  31.709999  31.160000  31.230000  ...  12582500  31.639999   1.169582
# 2020-01-07  32.700001  31.719999  31.799999  ...  13712900  32.540001   9.699977
# 2020-01-08  33.400002  32.349998  32.349998  ...  14632400  33.049999  14.218360
# ...               ...        ...        ...  ...       ...        ...        ...
# 2020-08-11  39.000000  36.709999  37.590000  ...  20486000  37.279999  58.645030
# 2020-08-12  38.000000  36.820000  37.500000  ...  11013300  37.439999  59.532873
# 2020-08-13  38.270000  37.369999  37.430000  ...  13259400  37.820000  61.639293
# 2020-08-14  37.959999  37.279999  37.740002  ...  10377300  37.900002  62.086731
# 2020-08-17  38.090000  37.270000  37.950001  ...  10186900  37.970001  62.498897

Analyzing multiple stocks and showing only the RSI signals

We’ll do a quick refactor in order to:

  1. Being able to analyze a list of multiple stocks
  2. Display only the RSI signal ( Neutral, Buy, or Sell ).

We create a new list for all our tickers, for example:

tickerList = ['MU','INTC', 'BP', 'TROX', 'MMM', 'UL', 'SFTBY', 'CRSR']

We create a new method named calc_rsi(t) that accepts one parameter, the list of tickers. Then we create a loop and iterate through all elements of the list:

def calc_rsi(t):
    # RSI code implementation goes here

for t in tickerList:
	calc_rsi(t)

Finally we need to output this data in a meaningful way, I’ve chosen to just display a different message depending if the stock is neutral, overbought, or oversold:

	length_of_dataframe = len(ticker)
	latest_rsi_value = ticker['RSI'][length_of_dataframe-1]
	oversold = 30
	overbought = 70

	if latest_rsi_value.astype(int) > overbought:
		print(t + ' RSI: ' + str(latest_rsi_value) + ' - SELL signal')
	elif latest_rsi_value.astype(int) < oversold:
		print(t + ' RSI: ' + str(latest_rsi_value) + ' - BUY signal')
	else:
		print(t + ' RSI: ' + str(latest_rsi_value) + ' - Neutral signal')

End result:

What about plotting the results graphically?

Not even thinking about it, the core problem this script is trying to solve is precisely to move from a visual solution offered by multiple brokers, to a pure numerical one, which is not offered. If you’d like to see the visual representation of the RSI for a given stock, any broker ( or tools like Yahoo Finance or Google Finance ) will give you that data.

Next steps:

At this point the tool is 100% complete and functional, however there’s always improvements to be made, for example:

  • Create an UI
  • Multiple timeframes available – Now daily only
  • Set up the script on a cloud server with a recurring job.
  • Unit Tests
  • Alerts via Telegram, email, OSX notifications, or other apps.

Have more ideas, feedback, or recommendations? Please drop a comment below or submit directly to the project GitHub repository here:

Click on the image so you’re redirected to GitHub

If you enjoyed the article and feel inclined to donate, you can do so here:

One-Time
Monthly
Yearly

Choose an amount

$1.00
$5.00
$10.00
$1.00
$5.00
$10.00
$1.00
$5.00
$10.00

Or enter a custom amount

$

Your contribution is appreciated.

Your contribution is appreciated.

Your contribution is appreciated.

DonateDonate monthlyDonate yearly

Leave a Reply