Connors RSI (CRSI)

References

Definition

  • Connors RSI (CRSI) is a technical analysis indicator created by Larry Connors that is actually a composite of three separate components.
  • Connors RSI was developed by Connors Research.
  • The three components; The RSI, UpDown Length, and Rate-of-Change, combine to form a momentum oscillator. Connors RSI outputs a value between 0 and 100, which is then used to identify short-term overbought and oversold conditions.

Calculation


There are three major components to Connors RSI

  • RSI = Standard RSI developed by Wilder. This is typically a short-term RSI. In this example it is a 3 Period RSI.
  • UpDown Length = The number of consecutive days that a security price has either closed up (higher than previous day) or closed down (lower than previous days). Closing up values represented in positive numbers and closing down is represented with negative numbers. If a security closes at the same price on back to back days, the UpDown Length is 0. Connors RSI then applies a short-term RSI to the UpDown Streak Value. In this example it is a 2 period RSI.
  • ROC = The Rate-of-Change. The ROC takes a user-defined look-back period and calculates a percentage of the number of values within that look back period that are below the current day price change percentage.

The final CRSI calculation then simply finding the average value of the three components.

CRSI(3,2,100) = [ RSI(3) + RSI(UpDown Length,2) + ROC(100) ] / 3


Read the indicator

Connors RSI (CRSI) uses the above formula to generate a value between 0 and 100. This is primarily used to identify overbought and oversold levels. Connor’s original definition of these levels is that a value over 90 should be considered overbought and a value under 10 should be considered oversold. On occasion, signals occur during slight corrections during a trend. For example, when the market is in an uptrend, Connors RSI might generate short term sell signals. When the market is in a downtrend, Connors RSI might generate short term buy signals.

A technical analyst should also be aware of the value of adapting or tweaking the Connor RSI. One of the issues with Connor RSI is that signals oftentimes occur early. For example, in an uptrend, a sell signal may present itself. However, the market continues to rise, thus a false signal. One potential safeguard against potential false signals would be combining the Connors RSI with additional technical analysis tools such as basic chart pattern analysis or additional indicators used to measure trend strength.

Another issue worth noting regarding the Connor RSI, is the placement of the overbought and oversold thresholds levels. For some trading instruments, the thresholds for overbought may need to be raised even higher and for oversold even lower. For example 95 and 5 respectively. These levels should generally be set after research and historical analysis. Making sure thresholds are in the proper place, should also help to cut down on false signals.

  • Connors RSI is designed to define overbought and oversold levels and therefore trade signals based on those levels.
    • A bullish signal occurs when Connors RSI enters oversold territory.
    • A bearish signal occurs when Connors RSI enters overbought territory.

Connors RSI indicator is a tool that takes a well established indicator, The Relative Strength Index (RSI) and applies it to its own theories. It can be a good way to define overbought and oversold levels and identify possible trading opportunities. That being said, Connors RSI does have a tendency to produce false signals. Therefore an astute technical analyst should experiment with what parameters work best for the security being traded. Also, combining Connors RSI with additional indicators will potentially increase its efficiency.

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-10 23:39:47.402768 ^GSPC (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 23:39:47.758161 GSK (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 23:39:48.036020 NVO (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 23:39:48.388309 PFE (5710, 7) 1999-12-31 00:00:00 2022-09-09 00:00:00
2022-09-10 23:39:48.621798 DAL (3867, 7) 2007-05-03 00:00:00 2022-09-09 00:00:00
ticker = 'DAL'
dfs[ticker].tail(5)
Open High Low Close Volume Dividends Stock Splits
Date
2022-09-02 31.440001 31.830000 30.700001 30.940001 8626500 0.0 0
2022-09-06 31.340000 31.650000 30.660000 31.190001 7630800 0.0 0
2022-09-07 31.290001 32.340000 31.270000 32.230000 9035900 0.0 0
2022-09-08 31.719999 32.490002 31.549999 32.119999 11085400 0.0 0
2022-09-09 32.430000 32.759998 32.240002 32.660000 10958900 0.0 0
Define Connors RSI (CRSI) calculation function
   def cal_rsi(
        ohlc: pd.DataFrame,
        period: int = 14,
        column: str = "close",
        adjust: bool = True,
    ) -> pd.Series:


        

        ## 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_crsi(
        ohlc: pd.DataFrame,
        rsi_period: int = 3, 
        updown_period: int = 2, 
        roc_period: int = 100, 
        column: str = "close",
        adjust: bool = True,
    ) -> pd.Series:

        """
        source: https://www.tradingview.com/script/iQQ4YgIY-Filtered-Connors-RSI-FCRSI/
        
        CRSI(3,2,100) = [ RSI(3) + RSI(UpDown Length,2) + ROC(100) ] / 3
        """
        
        def updown(s):
            ud = np.zeros(len(s))
            for i in range(1, len(s)):
                if s[i] > s[i-1]:
                    if ud[i-1] >= 0:
                        ud[i] = ud[i-1] + 1
                    else:
                        ud[i] = 1
                elif s[i] < s[i-1]:
                    if ud[i-1]<=0:
                        ud[i] = ud[i-1] - 1
                    else:
                        ud[i] = -1
                else:
                    ud[i] = 0
            return ud

        rsi3 = cal_rsi(ohlc, period=rsi_period, column=column, adjust=True)
        ohlc['updown'] = updown(ohlc[column])
        ud_rsi = cal_rsi(ohlc, period=updown_period, column='updown', adjust=True)
        
        roc = ohlc[column].diff() / ohlc[column].shift(1) * 100
        pct_rank = roc.rolling(window=roc_period, min_periods=roc_period).apply(lambda x: (x[:-1]<=x[-1]).sum()/roc_period)
        
        crsi = (rsi3 + ud_rsi + pct_rank)/3

        return pd.Series(crsi, name=f"CRSI")
Calculate Connors RSI (CRSI)
df = dfs[ticker][['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>
cal_crsi
<function __main__.cal_crsi(ohlc: pandas.core.frame.DataFrame, rsi_period: int = 3, updown_period: int = 2, roc_period: int = 100, column: str = 'close', adjust: bool = True) -> pandas.core.series.Series>
df['RSI'] = cal_rsi(df, period =3, column='Close')
df['CRSI'] = cal_crsi(df, rsi_period=3, updown_period=2, roc_period=100, column="Close")
display(df.head(5))
display(df.tail(5))
Open High Low Close Volume RSI updown CRSI
Date
2007-05-03 19.32 19.50 18.25 18.40 8052800 NaN 0.0 NaN
2007-05-04 18.88 18.96 18.39 18.64 5437300 100.000000 1.0 NaN
2007-05-07 18.83 18.91 17.94 18.08 2646300 22.222222 -1.0 NaN
2007-05-08 17.76 17.76 17.14 17.44 4166100 9.523810 -2.0 NaN
2007-05-09 17.54 17.94 17.44 17.58 7541100 23.809524 1.0 NaN
Open High Low Close Volume RSI updown CRSI
Date
2022-09-02 31.44 31.83 30.70 30.94 8626500 12.313048 -1.0 20.842173
2022-09-06 31.34 31.65 30.66 31.19 7630800 36.105527 1.0 36.171756
2022-09-07 31.29 32.34 31.27 32.23 9035900 76.274979 2.0 52.527472
2022-09-08 31.72 32.49 31.55 32.12 11085400 69.357083 -1.0 32.749111
2022-09-09 32.43 32.76 32.24 32.66 10958900 81.627385 1.0 47.978513
df[['CRSI', 'RSI']].hist(bins=50)
array([[<AxesSubplot:title={'center':'CRSI'}>,
        <AxesSubplot:title={'center':'RSI'}>]], 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
   

start = -50
end = df.shape[0]

names = {'main_title': f'{ticker}', 
         'sub_tile': 'Connors Relative Strength Index (CRSI)'}


aa_, bb_ = make_3panels2(df.iloc[start:end][['Open', 'High', 'Low', 'Close', 'Volume']], 
             df.iloc[start:end][['CRSI', 'RSI']], 
             chart_type='hollow_and_filled',names = names, 
                         fill_weights = (30, 70))

png