Relative Strength Index (RSI), Stochastic RSI (STOCH RSI), Inverse Fisher Transform applied on RSI (IFT RSI)
References
- fidelity: Relative Strength Index (RSI)
- tradingview: Relative Strength Index (RSI)
- tradingview: Stochastic RSI (STOCH RSI)
- finta
- THE INVERSE FISHER TRANSFORM
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)

#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

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))

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))
