Relative Strength Index (RSI), Stochastic RSI (STOCH RSI), Inverse Fisher Transform applied on RSI (IFT RSI)

References

Definition

Relative Strength Index (RSI)

  • The Relative Strength Index (RSI) is a momentum based oscillator used to measure the speed (velocity) and the change (magnitude) of directional price movements.
  • RSI provides a visual mean to monitor both the current and historical strength and weakness of a particular market.
    • The strength or weakness is based on closing prices over the duration of a specified trading period creating a reliable metric of price and momentum changes.

Stochastic RSI (Stoch RSI)

  • The Stochastic RSI indicator (Stoch RSI) is an indicator of an indicator. it is a measure of RSI relative to its own high/low range over a user defined period of time.
  • The Stochastic RSI is an oscillator that calculates a value between 0 and 1 which is then plotted as a line. This indicator is primarily used for identifying overbought and oversold conditions.

Inverse Fisher Transform RSI (IFT RSI)

  • Modified Inverse Fisher Transform applied on RSI (IFT RSI).
    • The result of using the Inverse Fisher Transform is that the output has a very high probability of being either +1 or –1. This bipolar probability distribution makes the Inverse Fisher Transform ideal for generating an indicator that provides clear buy and sell signals
  • The trading rules.
    • Buy when the indicator crosses over –0.5 or crosses over +0.5 if it has not previously crossed over –0.5.
    • sell short when the indicators crosses under +0.5 or crosses under –0.5 if it has not previously crossed under +0.5.

Calculation


RSI = 100 – 100/ (1 + RS)

  • RS = Average Gain of n days UP / Average Loss of n days DOWN

Stoch RSI = (RSI - Lowest Low RSI) / (Highest High RSI - Lowest Low RSI)


Read the indicator

Relative Strength Index (RSI)

  • RSI is a momentum based oscillator that operates between a scale of 0 and 100.
    • The closer RSI is to 0, the weaker the momentum is for price movements.
    • the closer RSI is to 100, the stronger the momentum.
  • 14 days is the most popular period, however traders have been known to use a wide variety of numbers of days.

  • Overbought/Oversold
    • RSI above 70 should be considered overbought
    • RSI below 30 should be considered oversold.
    • RSI between 30 and 70 should be considered neutral
    • RSI around 50 signified “no trend”.
    • Some traders believe that Wilder’s overbought/oversold ranges are too wide and choose to alter those ranges. For example, someone might consider any number above 80 as overbought and anything below 20 as oversold. This is entirely at the trader’s discretion.
  • Divergence: RSI Divergence occurs when there is a difference between what the price action is indicating and what RSI is indicating. These differences can be interpreted as an impending reversal. Specifically there are two types of divergences, bearish and bullish.
    • Bullish RSI Divergence – When price makes a new low but RSI makes a higher low.
    • Bearish RSI Divergence – When price makes a new high but RSI makes a lower high.
    • Wilder believed that Bearish Divergence creates a selling opportunity while Bullish Divergence creates a buying opportunity.

Stochastic RSI (Stoch RSI)

  • Overbought/Oversold
    • Stoch RSI above 0.80 considered overbought
    • Stoch RSI below 0.20 considered oversold.
    • overbought and oversold work best when trading along with the underlying trend.
Load basic packages
import pandas as pd
import numpy as np
import os
import gc
import copy
from pathlib import Path
from datetime import datetime, timedelta, time, date
#this package is to download equity price data from yahoo finance
#the source code of this package can be found here: https://github.com/ranaroussi/yfinance/blob/main
import yfinance as yf
pd.options.display.max_rows = 100
pd.options.display.max_columns = 100

import warnings
warnings.filterwarnings("ignore")

import pytorch_lightning as pl
random_seed=1234
pl.seed_everything(random_seed)
Global seed set to 1234





1234
#S&P 500 (^GSPC),  Dow Jones Industrial Average (^DJI), NASDAQ Composite (^IXIC)
#Russell 2000 (^RUT), Crude Oil Nov 21 (CL=F), Gold Dec 21 (GC=F)
#Treasury Yield 10 Years (^TNX)

#benchmark_tickers = ['^GSPC', '^DJI', '^IXIC', '^RUT',  'CL=F', 'GC=F', '^TNX']

benchmark_tickers = ['^GSPC']
tickers = benchmark_tickers + ['GSK', 'NVO', 'PFE', 'DAL']
#https://github.com/ranaroussi/yfinance/blob/main/yfinance/base.py
#     def history(self, period="1mo", interval="1d",
#                 start=None, end=None, prepost=False, actions=True,
#                 auto_adjust=True, back_adjust=False,
#                 proxy=None, rounding=False, tz=None, timeout=None, **kwargs):

dfs = {}

for ticker in tickers:
    cur_data = yf.Ticker(ticker)
    hist = cur_data.history(period="max", start='2000-01-01')
    print(datetime.now(), ticker, hist.shape, hist.index.min(), hist.index.max())
    dfs[ticker] = hist
2022-09-05 18:06:57.670458 ^GSPC (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:06:58.039240 GSK (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:06:58.351049 NVO (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:06:58.648695 PFE (5706, 7) 1999-12-31 00:00:00 2022-09-02 00:00:00
2022-09-05 18:06:58.879943 DAL (3863, 7) 2007-05-03 00:00:00 2022-09-02 00:00:00
ticker = 'DAL'
dfs[ticker].tail(5)
Open High Low Close Volume Dividends Stock Splits
Date
2022-08-29 32.200001 32.349998 31.850000 32.029999 8758400 0.0 0
2022-08-30 32.250000 32.450001 31.469999 31.719999 7506400 0.0 0
2022-08-31 31.969999 32.020000 31.059999 31.070000 7450000 0.0 0
2022-09-01 30.650000 31.139999 29.940001 31.090000 8572700 0.0 0
2022-09-02 31.440001 31.830000 30.700001 30.940001 8626500 0.0 0
Define Bollinger Bands calculation function

#https://github.com/peerchemist/finta/blob/af01fa594995de78f5ada5c336e61cd87c46b151/finta/finta.py


def cal_rsi(ohlc: pd.DataFrame, period: int = 14, column: str = "close", adjust: bool = True) -> pd.Series:
    """
    
    Relative Strength Index (RSI) is a momentum oscillator that measures the speed and change of price movements.
    RSI oscillates between zero and 100. Traditionally, and according to Wilder, RSI is considered overbought when above 70 and oversold when below 30.
    Signals can also be generated by looking for divergences, failure swings and centerline crossovers.
    RSI can also be used to identify the general trend.
    
    """
    
    ## get the price diff
    delta = ohlc[column].diff()

    ## positive gains (up) and negative gains (down) Series
    up, down = delta.copy(), delta.copy()
    up[up < 0] = 0
    down[down > 0] = 0

    # EMAs of ups and downs
    _gain = up.ewm(alpha=1.0 / period, adjust=adjust).mean()
    _loss = down.abs().ewm(alpha=1.0 / period, adjust=adjust).mean()

    RS = _gain / _loss
    return pd.Series(100 - (100 / (1 + RS)), name=f"RSI{period}")

def cal_ift_rsi(ohlc: pd.DataFrame, column: str = "close", rsi_period: int = 5, wma_period: int = 9) -> pd.Series:
    """
    Modified Inverse Fisher Transform applied on RSI.
    Suggested method to use any IFT indicator is to buy when the indicator crosses over –0.5 or crosses over +0.5
    if it has not previously crossed over –0.5 and to sell short when the indicators crosses under +0.5 or crosses under –0.5
    if it has not previously crossed under +0.5.
    
    """
    
    rsi = cal_rsi(ohlc, period=rsi_period,  column=column)
    v1 = .1 * (rsi - 50)

    # v2 = WMA(wma_period) of v1
    d = (wma_period * (wma_period + 1)) / 2  # denominator
    weights = np.arange(1, wma_period + 1)

    def linear(w):
        def _compute(x):
            return (w * x).sum() / d

        return _compute

    _wma = v1.rolling(wma_period, min_periods=wma_period)
    v2 = _wma.apply(linear(weights), raw=True)

    ift = pd.Series(((v2 ** 2 - 1) / (v2 ** 2 + 1)), name="IFT_RSI")

    return ift

def cal_stoch_rsi(ohlc: pd.DataFrame, column: str = "close", rsi_period: int = 14, stoch_period: int = 14) -> pd.Series:
    
        """
        stochastic RSI (StochRSI) is an oscillator that measures the level of RSI relative to its high-low range over a set time period.
        StochRSI applies the Stochastics formula to RSI values, instead of price values. This makes it an indicator of an indicator.
        The result is an oscillator that fluctuates between 0 and 1.
        
        """

        rsi = cal_rsi(ohlc, column=column,  period=rsi_period)
        stoch_rsi = ((rsi - rsi.min()) / (rsi.max() - rsi.min())).rolling(window=stoch_period).mean()
        
        return pd.Series(stoch_rsi, name="StochRSI")
Calculate MAMA
df = dfs['^GSPC'][['Open', 'High', 'Low', 'Close', 'Volume']]
df = df.round(2)
cal_rsi
<function __main__.cal_rsi(ohlc: pandas.core.frame.DataFrame, period: int = 14, column: str = 'close', adjust: bool = True) -> pandas.core.series.Series>
df_ta = cal_rsi(df, period = 14, column = 'Close')
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

del df_ta
gc.collect()
122
cal_ift_rsi
<function __main__.cal_ift_rsi(ohlc: pandas.core.frame.DataFrame, column: str = 'close', rsi_period: int = 5, wma_period: int = 9) -> pandas.core.series.Series>
df_ta = cal_ift_rsi(df, column = 'Close')
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

del df_ta
gc.collect()
42
cal_stoch_rsi
<function __main__.cal_stoch_rsi(ohlc: pandas.core.frame.DataFrame, column: str = 'close', rsi_period: int = 14, stoch_period: int = 14) -> pandas.core.series.Series>
df_ta = cal_stoch_rsi(df, column = 'Close', rsi_period = 14, stoch_period = 14)
df = df.merge(df_ta, left_index = True, right_index = True, how='inner' )

del df_ta
gc.collect()
42
display(df.head(5))
display(df.tail(5))
Open High Low Close Volume RSI14 IFT_RSI StochRSI
Date
1999-12-31 1464.47 1472.42 1458.19 1469.25 374050000 NaN NaN NaN
2000-01-03 1469.25 1478.00 1438.36 1455.22 931800000 0.000000 NaN NaN
2000-01-04 1455.22 1455.22 1397.43 1399.42 1009000000 0.000000 NaN NaN
2000-01-05 1399.42 1413.27 1377.68 1402.11 1085500000 4.038943 NaN NaN
2000-01-06 1402.11 1411.90 1392.10 1403.45 1092300000 6.074065 NaN NaN
Open High Low Close Volume RSI14 IFT_RSI StochRSI
Date
2022-08-29 4034.58 4062.99 4017.42 4030.61 2963020000 41.911634 0.378028 0.697439
2022-08-30 4041.25 4044.98 3965.21 3986.16 3190580000 38.820090 0.574336 0.673621
2022-08-31 4000.67 4015.37 3954.53 3955.00 3797860000 36.772360 0.682515 0.648440
2022-09-01 3936.73 3970.23 3903.65 3966.85 3754570000 38.109406 0.716614 0.620783
2022-09-02 3994.66 4018.43 3906.21 3924.26 4134920000 35.226179 0.752521 0.589997
df[['RSI14', 'IFT_RSI', 'StochRSI']].hist(bins=50)
array([[<AxesSubplot:title={'center':'RSI14'}>,
        <AxesSubplot:title={'center':'IFT_RSI'}>],
       [<AxesSubplot:title={'center':'StochRSI'}>, <AxesSubplot:>]],
      dtype=object)

png

#https://github.com/matplotlib/mplfinance
#this package help visualize financial data
import mplfinance as mpf
import matplotlib.colors as mcolors

# all_colors = list(mcolors.CSS4_COLORS.keys())#"CSS Colors"
# all_colors = list(mcolors.TABLEAU_COLORS.keys()) # "Tableau Palette",
all_colors = ['dodgerblue', 'firebrick','limegreen','skyblue','lightgreen',  'navy','yellow','plum',  'yellowgreen']
# all_colors = list(mcolors.BASE_COLORS.keys()) #"Base Colors",


#https://github.com/matplotlib/mplfinance/issues/181#issuecomment-667252575
#list of colors: https://matplotlib.org/stable/gallery/color/named_colors.html
#https://github.com/matplotlib/mplfinance/blob/master/examples/styles.ipynb

def make_3panels2(main_data, mid_panel, chart_type='candle', names=None, 
                  figratio=(14,9), fill_weights = (0, 0)):
    """
    main chart type: default is candle. alternatives: ohlc, line

    example:
    start = 200

    names = {'main_title': 'MAMA: MESA Adaptive Moving Average', 
             'sub_tile': 'S&P 500 (^GSPC)', 'y_tiles': ['price', 'Volume [$10^{6}$]']}


    make_candle(df.iloc[-start:, :5], df.iloc[-start:][['MAMA', 'FAMA']], names = names)
    
    """

    style = mpf.make_mpf_style(base_mpf_style='yahoo',  #charles
                               base_mpl_style = 'seaborn-whitegrid',
#                                marketcolors=mpf.make_marketcolors(up="r", down="#0000CC",inherit=True),
                               gridcolor="whitesmoke", 
                               gridstyle="--", #or None, or - for solid
                               gridaxis="both", 
                               edgecolor = 'whitesmoke',
                               facecolor = 'white', #background color within the graph edge
                               figcolor = 'white', #background color outside of the graph edge
                               y_on_right = False,
                               rc =  {'legend.fontsize': 'small',#or number
                                      #'figure.figsize': (14, 9),
                                     'axes.labelsize': 'small',
                                     'axes.titlesize':'small',
                                     'xtick.labelsize':'small',#'x-small', 'small','medium','large'
                                     'ytick.labelsize':'small'
                                     }, 
                              )   

    if (chart_type is None) or (chart_type not in ['ohlc', 'line', 'candle', 'hollow_and_filled']):
        chart_type = 'candle'
    len_dict = {'candle':2, 'ohlc':3, 'line':1, 'hollow_and_filled':2}    
        
    kwargs = dict(type=chart_type, figratio=figratio, volume=True, volume_panel=2, 
                  panel_ratios=(4,2,1), tight_layout=True, style=style, returnfig=True)
    
    if names is None:
        names = {'main_title': '', 'sub_tile': ''}
    


    added_plots = { }
  
    fb_bbands2_ = dict(y1=fill_weights[0]*np.ones(mid_panel.shape[0]),
                      y2=fill_weights[1]*np.ones(mid_panel.shape[0]),color="lightskyblue",alpha=0.1,interpolate=True)
    fb_bbands2_['panel'] = 1

    fb_bbands= [fb_bbands2_]
    
    
    i = 0
    for name_, data_ in mid_panel.iteritems():
        added_plots[name_] = mpf.make_addplot(data_, panel=1, width=1, color=all_colors[i], secondary_y=False)
        i = i + 1
    

    fig, axes = mpf.plot(main_data,  **kwargs,
                         addplot=list(added_plots.values()), 
                         fill_between=fb_bbands)
    # add a new suptitle
    fig.suptitle(names['main_title'], y=1.05, fontsize=12, x=0.1285)

    axes[0].set_title(names['sub_tile'], fontsize=10, style='italic',  loc='left')
#     axes[2].set_ylabel('WAVEPM10')

#     axes[0].set_ylabel(names['y_tiles'][0])
#     axes[2].set_ylabel(names['y_tiles'][1])
    return fig, axes
   
w

png


start = -300
end = -1

names = {'main_title': f'{ticker}', 
         'sub_tile': 'Stochastic RSI (STOCH RSI): above 0.80 considered overbought; below 0.20 considered oversold.'}


aa_, bb_ = make_3panels2(df.iloc[start:end][['Open', 'High', 'Low', 'Close', 'Volume']], 
             df.iloc[start:end][['StochRSI']], 
             chart_type='hollow_and_filled',names = names, 
                         fill_weights = (0.3, 0.7))

png


start = -200
end = -1

names = {'main_title': f'{ticker}', 
         'sub_tile': 'Inverse Fisher Transform applied on RSI: Buy when the indicator crosses over –0.5 or crosses over +0.5 if it has not previously crossed over –0.5'}


aa_, bb_ = make_3panels2(df.iloc[start:end][['Open', 'High', 'Low', 'Close', 'Volume']], 
             df.iloc[start:end][['IFT_RSI']], 
             chart_type='hollow_and_filled',names = names, 
                         fill_weights = (-0.5, 0.5))

png