Connors RSI (CRSI)
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 , Streak RSI period , Rank period
- Value Range: 0 — 100
- RSI(Close, 3): Short-term price momentum
- RSI(Streak, 2): Momentum of consecutive up/down streak
- PercentRank(ROC(1), 100): Percentile rank of today’s return over the past 100 days
II. Mathematical Principles and Calculation
Core Formula
Component 1: RSI(Close, 3)
Standard RSI formula with period shortened to 3:
Component 2: RSI(Streak, 2)
Streak (consecutive up/down count) definition:
- If , Streak increments by 1 (from 0 or positive)
- If , Streak decrements by 1 (from 0 or negative)
- If , 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:
Component 3: PercentRank(ROC(1), 100)
ROC(1): Single-day return
PercentRank: Percentile rank of current ROC(1) among the past 100 ROC(1) values
Step-by-Step Calculation Logic
- Calculate RSI(Close, 3): 3-period RSI using Wilder Smoothing
- Build Streak series: Calculate the consecutive up/down count for each day
- Calculate RSI(Streak, 2): 2-period RSI on the Streak series
- Calculate ROC(1): Daily percentage return
- Calculate PercentRank: Rank of current ROC(1) within the past 100 periods
- Composite: Simple average of all three
- 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
| Component | Dimension | Information |
|---|---|---|
| RSI(3) | Price momentum | Speed of short-term price changes |
| RSI(Streak, 2) | Persistence | Strength of consecutive up/down sequences |
| PercentRank | Historical position | Historical 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.
V. Advantages, Disadvantages, and Use Cases
Advantages
| Advantage | Description |
|---|---|
| Multi-dimensional fusion | Combines three independent dimensions: price, persistence, and historical rank |
| Ultra-short-term sensitivity | RSI(3) and RSI(Streak, 2) with very short periods react extremely fast |
| Mean reversion specialized | Specifically optimized for short-term reversal strategies |
| Clear signals | Extreme values (< 10 or > 90) appear at moderate frequency with high win rates |
Disadvantages
| Disadvantage | Description |
|---|---|
| Fails in trending markets | Counter-trend signals during sustained trends can cause large losses |
| Short-term only | Designed for holding periods of 1-5 days |
| Requires trend filter | Cannot be used independently; must be combined with trend analysis |
| Complex calculation | Three 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
Comparison with Related Indicators
| Comparison | CRSI | RSI(14) | RSI(2) | StochRSI |
|---|---|---|---|---|
| Period | Ultra-short | Medium | Ultra-short | Medium-derived |
| Dimensions | Three | One | One | Second-order |
| Strategy Type | Mean Reversion | General | Mean Reversion | General |
| Thresholds | 10/90 | 30/70 | 5/95 | 20/80 |
tip Practical Tips
- Must add trend filter: Only consider CRSI oversold buy signals when price is above the 200-day moving average.
- Extreme thresholds: Using CRSI < 5 or > 95 yields higher win rates, but trading frequency drops significantly.
- Profit-taking rules: Connors suggests taking profit on longs when CRSI > 70, and covering shorts when CRSI < 30.
- Suited for systematic execution: CRSI strategy rules are clear and well-suited for automated trading programs.