Connors RSI (CRSI)

Haiyue
11min

I. What is Connors RSI

Connors RSI (CRSI) is a composite momentum indicator that synthesizes three independent momentum components into a single oscillator. It combines the RSI of short-term price changes, the RSI of consecutive up/down day streaks, and the percentile rank of recent price changes, aiming to capture short-term price reversal opportunities more precisely than traditional RSI.

Historical Background

CRSI was developed by Larry Connors and Cesar Alvarez, published across several of Connors’ books including Short Term Trading Strategies That Work. Larry Connors is the founder of TradingMarkets.com and is renowned for researching and promoting short-term mean reversion strategies. He found that traditional RSI(14) is too slow for short-term trading, while very short-period RSI (such as RSI(2)) is sensitive but one-dimensional, so he designed CRSI to fuse multiple dimensions of short-term momentum information.

Indicator Classification

  • Type: Oscillator, displayed in a separate panel
  • Category: Momentum — composite indicator
  • Default Parameters: RSI period =3= 3, Streak RSI period =2= 2, Rank period =100= 100
  • Value Range: 0 — 100
Three Components of CRSI
  1. RSI(Close, 3): Short-term price momentum
  2. RSI(Streak, 2): Momentum of consecutive up/down streak
  3. PercentRank(ROC(1), 100): Percentile rank of today’s return over the past 100 days

II. Mathematical Principles and Calculation

Core Formula

CRSI=RSI(C,3)+RSI(Streak,2)+PercentRank(ROC1,100)3CRSI = \frac{RSI(C, 3) + RSI(Streak, 2) + PercentRank(ROC_1, 100)}{3}

Component 1: RSI(Close, 3)

Standard RSI formula with period shortened to 3:

RSI3=1001001+RS3RSI_3 = 100 - \frac{100}{1 + RS_3}

Component 2: RSI(Streak, 2)

Streak (consecutive up/down count) definition:

  • If Ct>Ct1C_t > C_{t-1}, Streak increments by 1 (from 0 or positive)
  • If Ct<Ct1C_t < C_{t-1}, Streak decrements by 1 (from 0 or negative)
  • If Ct=Ct1C_t = C_{t-1}, Streak resets to zero

Example: 3 consecutive up days — Streak = 3; 2 consecutive down days — Streak = -2.

Then calculate RSI(2) on the Streak series:

RSIStreak=RSI(Streak,2)RSI_{Streak} = RSI(Streak, 2)

Component 3: PercentRank(ROC(1), 100)

ROC(1): Single-day return

ROC1=CtCt1Ct1×100ROC_1 = \frac{C_t - C_{t-1}}{C_{t-1}} \times 100

PercentRank: Percentile rank of current ROC(1) among the past 100 ROC(1) values

PercentRank=count of past 100 values less than current ROC(1)100×100PercentRank = \frac{\text{count of past 100 values less than current ROC(1)}}{100} \times 100

Step-by-Step Calculation Logic

  1. Calculate RSI(Close, 3): 3-period RSI using Wilder Smoothing
  2. Build Streak series: Calculate the consecutive up/down count for each day
  3. Calculate RSI(Streak, 2): 2-period RSI on the Streak series
  4. Calculate ROC(1): Daily percentage return
  5. Calculate PercentRank: Rank of current ROC(1) within the past 100 periods
  6. Composite: Simple average of all three
Why Three Components?
  • RSI(3) captures the absolute level of short-term price momentum
  • RSI(Streak, 2) captures the persistence/continuity of price movement
  • PercentRank captures the historical rarity of today’s change

Each has a different focus; combined, they provide a more comprehensive short-term momentum profile.


III. Python Implementation

import numpy as np
import pandas as pd


def rsi_wilder(series: pd.Series, period: int) -> pd.Series:
    """Calculate RSI using Wilder Smoothing"""
    delta = series.diff()
    gain = delta.where(delta > 0, 0.0)
    loss = (-delta).where(delta < 0, 0.0)

    avg_gain = gain.copy()
    avg_loss = loss.copy()
    avg_gain.iloc[:period] = np.nan
    avg_loss.iloc[:period] = np.nan

    first_idx = period
    avg_gain.iloc[first_idx] = gain.iloc[1:first_idx + 1].mean()
    avg_loss.iloc[first_idx] = loss.iloc[1:first_idx + 1].mean()

    for i in range(first_idx + 1, len(series)):
        avg_gain.iloc[i] = (avg_gain.iloc[i - 1] * (period - 1) + gain.iloc[i]) / period
        avg_loss.iloc[i] = (avg_loss.iloc[i - 1] * (period - 1) + loss.iloc[i]) / period

    rs = avg_gain / avg_loss
    return 100 - 100 / (1 + rs)


def calc_streak(close: pd.Series) -> pd.Series:
    """Calculate consecutive up/down day streak"""
    streak = pd.Series(0, index=close.index, dtype=float)
    for i in range(1, len(close)):
        if close.iloc[i] > close.iloc[i - 1]:
            streak.iloc[i] = max(streak.iloc[i - 1], 0) + 1
        elif close.iloc[i] < close.iloc[i - 1]:
            streak.iloc[i] = min(streak.iloc[i - 1], 0) - 1
        else:
            streak.iloc[i] = 0
    return streak


def percent_rank(series: pd.Series, period: int) -> pd.Series:
    """Calculate percentile rank"""
    def rank_func(x):
        current = x[-1]
        past = x[:-1]
        return np.sum(past < current) / len(past) * 100
    return series.rolling(window=period + 1, min_periods=period + 1).apply(
        rank_func, raw=True
    )


def connors_rsi(close: pd.Series,
                rsi_period: int = 3,
                streak_period: int = 2,
                rank_period: int = 100) -> pd.DataFrame:
    """
    Calculate Connors RSI

    Parameters
    ----------
    close : pd.Series
        Close price series
    rsi_period : int
        Price RSI period, default 3
    streak_period : int
        Streak RSI period, default 2
    rank_period : int
        PercentRank lookback period, default 100

    Returns
    -------
    pd.DataFrame
        DataFrame with RSI, RSI_Streak, PercentRank, and CRSI columns
    """
    # Component 1: RSI(Close, 3)
    rsi_close = rsi_wilder(close, rsi_period)

    # Component 2: RSI(Streak, 2)
    streak = calc_streak(close)
    rsi_streak = rsi_wilder(streak, streak_period)

    # Component 3: PercentRank(ROC(1), 100)
    roc1 = close.pct_change() * 100
    pct_rank = percent_rank(roc1, rank_period)

    # Composite
    crsi = (rsi_close + rsi_streak + pct_rank) / 3

    return pd.DataFrame({
        "RSI_3": rsi_close,
        "RSI_Streak": rsi_streak,
        "PctRank": pct_rank,
        "CRSI": crsi
    }, index=close.index)


# ========== Usage Example ==========
if __name__ == "__main__":
    np.random.seed(42)
    dates = pd.date_range("2024-01-01", periods=200, freq="D")
    price = 100 + np.cumsum(np.random.randn(200) * 1.0)

    df = pd.DataFrame({
        "date": dates,
        "open":  price + np.random.randn(200) * 0.3,
        "high":  price + np.abs(np.random.randn(200) * 0.6),
        "low":   price - np.abs(np.random.randn(200) * 0.6),
        "close": price,
        "volume": np.random.randint(1000, 10000, size=200),
    })
    df.set_index("date", inplace=True)

    # Calculate Connors RSI
    result = connors_rsi(df["close"])
    df = pd.concat([df, result], axis=1)

    print("=== Connors RSI Results (last 15 rows) ===")
    print(df[["close", "RSI_3", "RSI_Streak", "PctRank", "CRSI"]].tail(15).to_string())

    # Mean reversion signals
    df["signal"] = np.where(
        df["CRSI"] < 10, 1,      # Extremely oversold -> Buy
        np.where(df["CRSI"] > 90, -1, 0)  # Extremely overbought -> Sell
    )

    print("\n=== Extremely Oversold Signals (CRSI < 10) ===")
    print(df[df["signal"] == 1][["close", "CRSI"]].head(10).to_string())
    print("\n=== Extremely Overbought Signals (CRSI > 90) ===")
    print(df[df["signal"] == -1][["close", "CRSI"]].head(10).to_string())

IV. Problems the Indicator Solves

1. Short-Term Mean Reversion Timing

CRSI is specifically designed for short-term mean reversion strategies:

  • CRSI < 10: All three dimensions show extreme oversold — strong buy signal
  • CRSI > 90: All three dimensions show extreme overbought — strong sell signal

2. Multi-Dimensional Momentum Assessment

ComponentDimensionInformation
RSI(3)Price momentumSpeed of short-term price changes
RSI(Streak, 2)PersistenceStrength of consecutive up/down sequences
PercentRankHistorical positionHistorical rarity of today’s change

3. Quantifying Consecutive Streaks

Traditional indicators ignore the “continuity” information of price movement. Through the Streak component, CRSI can identify overbought/oversold conditions after consecutive days of rise or decline.

4. Relative Position Assessment

The PercentRank component answers a key question: “Where does today’s change rank historically?” If the rank is extremely low (today’s decline is worse than most of the past 100 days), it suggests oversold conditions.

Caution

V. Advantages, Disadvantages, and Use Cases

Advantages

AdvantageDescription
Multi-dimensional fusionCombines three independent dimensions: price, persistence, and historical rank
Ultra-short-term sensitivityRSI(3) and RSI(Streak, 2) with very short periods react extremely fast
Mean reversion specializedSpecifically optimized for short-term reversal strategies
Clear signalsExtreme values (< 10 or > 90) appear at moderate frequency with high win rates

Disadvantages

DisadvantageDescription
Fails in trending marketsCounter-trend signals during sustained trends can cause large losses
Short-term onlyDesigned for holding periods of 1-5 days
Requires trend filterCannot be used independently; must be combined with trend analysis
Complex calculationThree components each with their own steps; relatively complex implementation

Use Cases

  • Short-term mean reversion strategies: Buying pullbacks in uptrends, selling bounces in downtrends
  • ETF trading: CRSI performs excellently on liquid ETFs
  • 1-5 day holding period: Connors’ backtests show CRSI works best with short holding periods
ComparisonCRSIRSI(14)RSI(2)StochRSI
PeriodUltra-shortMediumUltra-shortMedium-derived
DimensionsThreeOneOneSecond-order
Strategy TypeMean ReversionGeneralMean ReversionGeneral
Thresholds10/9030/705/9520/80

tip Practical Tips

  1. Must add trend filter: Only consider CRSI oversold buy signals when price is above the 200-day moving average.
  2. Extreme thresholds: Using CRSI < 5 or > 95 yields higher win rates, but trading frequency drops significantly.
  3. Profit-taking rules: Connors suggests taking profit on longs when CRSI > 70, and covering shorts when CRSI < 30.
  4. Suited for systematic execution: CRSI strategy rules are clear and well-suited for automated trading programs.