FabTrader_Logo_Main
  • Home
  • Courses
    • Build Algo Trading Platform using Python Course
  • Store
  • Tools
  • Stories
  • About Me
Edit Content
FabTrader_Logo_Main
Welcome to FabTrader Community!

Course Dashboard
Store Account
Cart
Algo Trading Course
Other Courses
About Me
Store
Stories

Get in touch with me

hello@fabtrader.in

blog banner background shape images
  • FabTrader
  • March 20, 2025

Build & Use Relative Rotation Graphs (RRG) for Smarter Investing

  • 10 min read
  • 371 Views

In the world of investing and trading, identifying market leaders and laggards is crucial. Traditional price charts and indicators provide valuable insights, but they often fail to visualize sector or stock rotation in a clear and intuitive manner. This is where Relative Rotation Graphs (RRG) come into play.

RRG charts allow traders and investors to compare the relative strength and momentum of multiple securities against a benchmark, revealing how different stocks, sectors, or asset classes are rotating through various performance phases. This article explores how to interpret RRGs effectively and use them for making informed investment decisions.

What is a Relative Rotation Graph (RRG)?

RRG is a visualization tool that plots securities based on two key indicators:

  1. JdK RS-Ratio (Relative Strength Ratio) – Measures a security’s relative strength against a benchmark. Higher values indicate outperformance, while lower values suggest underperformance.
  2. JdK RS-Momentum (Relative Strength Momentum) – Measures the rate of change of the relative strength. Increasing momentum suggests improving strength, while decreasing momentum signals weakening performance.

By plotting these values on a two-dimensional plane, RRG divides the chart into four quadrants:

Improving (Look for Buys) – Top-left quadrant
  • Securities in this quadrant have weak relative strength but are gaining momentum.
  • These are potential turnaround candidates that may soon transition into the leading quadrant.
  • Ideal for investors looking for early entry points into emerging leaders.
Leading (Hold) – Top-right quadrant
  • Securities in this quadrant have both high relative strength and strong momentum.
  • These are the outperformers of the market.
  • Typically, stocks or sectors here continue to perform well, making them attractive for holding or further accumulation.
Weakening (Look for Sells) – Bottom-right quadrant
  • Securities in this quadrant still have high relative strength but are losing momentum.
  • This often indicates a mature uptrend that may be slowing down.
  • If a security remains in this quadrant for an extended period, it may transition into the lagging quadrant, signaling a potential exit point.
Lagging (Avoid) – Bottom-left quadrant
  • Securities in this quadrant exhibit both weak relative strength and weak momentum.
  • These are underperformers that should be avoided or shorted.
  • Investors should be cautious about bottom fishing unless there is a clear sign of reversal.

Python Implementation

Following is the python – Streamlit implementation of the RRG chart.

import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import timedelta, date

st.set_page_config(layout="wide")
st.subheader("Relative Rotation Graph (RRG) Analysis")
st.markdown("""<style>
            header[data-testid="stHeader"] {display:none;}
            </style>""", unsafe_allow_html=True)
# Sidebar inputs
st.sidebar.header("Input Parameters")

#### Initial setup - Broker Connection ####
pd.set_option("display.max_rows", None, "display.max_columns", None)

# Benchmark ticker input
benchmark_ticker = st.sidebar.text_input("Benchmark Ticker :", "NIFTY 50")

# Ticker input - multiple tickers separated by commas
ticker_input = st.sidebar.text_area(
    "Enter Tickers (comma separated):",
    value="NIFTY AUTO,NIFTY BANK,NIFTY ENERGY,NIFTY FMCG,NIFTY IT,NIFTY MEDIA,NIFTY METAL,NIFTY PHARMA, NIFTY PSU BANK, NIFTY REALTY",
    height=200
)

tickers = [ticker.strip() for ticker in ticker_input.split(",")]

# Period selector with slider
max_days = 365
period_days = st.sidebar.slider(
    "Period (days):",
    min_value=30,
    max_value=max_days,
    value=180,
    step=10
)

# Tail length selector
tail_length = st.sidebar.slider(
    "Tail Length (days):",
    min_value=1,
    max_value=60,
    value=5,
    step=1
)

show_label = st.sidebar.checkbox("Show Ticker Label", value=True)

# Function to calculate RRG data
def calculate_rrg(tickers, benchmark, period_days, tail_length):
    end_date = date.today()
    start_date = end_date - timedelta(days=period_days + 25)  # Extra buffer for calculations

    # Download data
    try:
        tickers_data = pd.DataFrame()

        ## Replace this code below with your code to fetch historical candlestick data
        benchmark_data = Instruments.get_historical_data(benchmark, start_date, end_date)['Close']
        ## Replace this code below with your code to fetch historical candlestick data

        for symbol in tickers:
            df = Instruments.get_historical_data(symbol, start_date, end_date)
            if not df.empty:
                tickers_data[symbol] = df['Close']

        # Ensure we have data for all tickers
        if isinstance(tickers_data, pd.Series):
            tickers_data = pd.DataFrame(tickers_data)
            tickers_data.columns = [tickers[0]]

        # Calculate relative strength
        rs_data = pd.DataFrame()
        jdK_data = pd.DataFrame()
        jdD_data = pd.DataFrame()

        for ticker in tickers_data.columns:
            # Relative Strength = Price of Stock / Price of Benchmark * 100
            rs_data[ticker] = (tickers_data[ticker] / benchmark_data) * 100

            # Normalize to 100 at the start
            rs_data[ticker] = 100 * rs_data[ticker] / rs_data[ticker].iloc[30]

            # JdK RS-Ratio (X-axis): 13-period EMA of RS divided by 21-period EMA of RS
            jdK_data[ticker] = 100 * (rs_data[ticker].ewm(span=13).mean() /
                                      rs_data[ticker].ewm(span=21).mean())

            # JdK RS-Momentum (Y-axis): 13-period RoC of RS-Ratio
            jdD_data[ticker] = 100 * (jdK_data[ticker] / jdK_data[ticker].shift(13) - 1)

        # Remove NaN values
        jdK_data = jdK_data.dropna()
        jdD_data = jdD_data.dropna()

        # Get the most recent data points
        recent_data = pd.DataFrame({
            'RS-Ratio': jdK_data.iloc[-1],
            'RS-Momentum': jdD_data.iloc[-1]
        })

        # Get tail data for each ticker
        tail_data = {}
        for ticker in tickers_data.columns:
            tail_data[ticker] = {
                'x': jdK_data[ticker].iloc[-tail_length:].values,
                'y': jdD_data[ticker].iloc[-tail_length:].values
            }

        return recent_data, tail_data, True
    except Exception as e:
        st.error(f"Error fetching or calculating data: {e}")
        return None, None, False

# Calculate RRG data
recent_data, tail_data, success = calculate_rrg(tickers, benchmark_ticker, period_days, tail_length)

# Main content
if success and recent_data is not None:
    st.write(f"RRG Analysis: Relative to {benchmark_ticker}")

    # Create RRG plot
    fig, ax = plt.subplots(figsize=(12, 6))

    # Enhance data points by scaling from the center (100, 0)
    enhanced_data = recent_data.copy()

    # Also scale tail data
    enhanced_tail_data = {}

    for ticker in tail_data:
        enhanced_tail_data[ticker] = {
            'x': tail_data[ticker]['x'],
            'y': tail_data[ticker]['y']
        }

    # Draw quadrant lines and labels
    ax.axhline(y=0, color='gray', linestyle='-', alpha=0.5)
    ax.axvline(x=100, color='gray', linestyle='-', alpha=0.5)

    # Set quadrant labels - positioned further out to accommodate scaled data
    label_offset = 4
    ax.text(100 + label_offset, label_offset / 2, "Leading\n(Hold)", ha='center', va='center', fontsize=7,
            bbox=dict(facecolor='yellow', alpha=0.2))
    ax.text(100 - label_offset, label_offset / 2, "Improving\n(Look to Buy)", ha='center', va='center',
            fontsize=7, bbox=dict(facecolor='red', alpha=0.2))
    ax.text(100 - label_offset, -label_offset / 2, "Lagging\n(Avoid)", ha='center', va='center', fontsize=7,
            bbox=dict(facecolor='gray', alpha=0.2))
    ax.text(100 + label_offset, -label_offset / 2, "Weakening\n(Look to Sell)", ha='center', va='center',
            fontsize=7, bbox=dict(facecolor='green', alpha=0.2))

    # Plot current positions
    colors = plt.cm.tab10(np.linspace(0, 1, len(tickers)))

    for i, ticker in enumerate(tickers):
        if ticker in enhanced_data.index:
            # Plot tails
            if ticker in enhanced_tail_data:
                ax.plot(enhanced_tail_data[ticker]['x'], enhanced_tail_data[ticker]['y'],
                        '-', color=colors[i], alpha=0.6, linewidth=1)

            # Plot current position
            x = enhanced_data.loc[ticker, 'RS-Ratio']
            y = enhanced_data.loc[ticker, 'RS-Momentum']
            ax.scatter(x, y, color=colors[i], s=100, label=ticker)
            if show_label:
                ax.text(x , y , ticker, fontsize=7, ha='center')

    # Set axis limits to create equal quadrants - with wider boundaries for scaled data
    ratio_max = max(abs(enhanced_data['RS-Ratio'].max() - 100), 1)
    momentum_max = max(abs(enhanced_data['RS-Momentum'].max()), abs(enhanced_data['RS-Momentum'].min()),
                       2)

    ax.set_xlim(100 - ratio_max - 5, 100 + ratio_max + 5)
    ax.set_ylim(-momentum_max-0.25, momentum_max+0.25)

    # Add details
    ax.set_title(
        f'Relative Rotation Graph (Period: {period_days} days, Tail: {tail_length} days',
        fontsize=9)
    ax.set_xlabel('RS-Ratio (Relative Strength)', fontsize=7)
    ax.set_ylabel('RS-Momentum', fontsize=7)
    ax.grid(True, alpha=0.3)
    ax.legend(loc='upper left', bbox_to_anchor=(1, 1))

    st.pyplot(fig)

    # Add explanation
    with st.expander("Understanding RRG Quadrants"):
        st.markdown("""
            **Leading (Top Right)** - Strong relative strength and positive momentum. These are typically your leaders and best performers.

            **Weakening (Bottom Right)** - Still strong but losing momentum. These might be stocks to consider selling as they rotate into the lagging quadrant.

            **Lagging (Bottom Left)** - Weak relative strength and negative momentum. These are typically underperformers.

            **Improving (Top Left)** - Still weak but gaining momentum. These might be stocks to consider buying as they rotate into the leading quadrant.

            """)
else:
    st.info("Enter valid tickers and parameters to generate the RRG plot.")



How to run this streamlit script

How to Interpret RRG Rotation Patterns

RRG charts provide more than just a snapshot; they also illustrate how securities rotate through different phases over time. By analyzing the tail movement, investors can gain deeper insights into market dynamics.

  1. Clockwise Rotation – The most common pattern
    • Securities tend to move from improving → leading → weakening → lagging in a clockwise fashion.
    • This rotation aligns with market cycles and sector rotation.
  2. Sharp Turns in Momentum
    • A sudden move from lagging to improving suggests a strong reversal.
    • Conversely, a sharp drop from leading to weakening may indicate an overbought condition.
  3. Length of the Tail
    • A long tail signifies high volatility and strong trends.
    • A short tail suggests stability but slower movement.
  4. Securities Crossing Quadrants
    • Stocks moving from improving to leading are prime buy candidates.
    • Stocks moving from weakening to lagging should be avoided.

Practical Applications of RRG

  1. Sector Rotation Strategy
    • Investors can use RRG to identify which sectors are gaining strength and allocate capital accordingly.
    • For example, if technology is moving from improving to leading, it may be a good time to increase exposure to tech stocks.
  2. Stock Selection
    • RRG helps traders focus on stocks with strong relative performance rather than just absolute price movements.
  3. Portfolio Diversification
    • By identifying sector rotations, investors can balance their portfolios across leading and improving sectors.
  4. Market Timing
    • Watching how securities rotate through quadrants helps investors time their entries and exits more effectively.

Limitations of RRG

While RRG is a powerful tool, it should not be used in isolation. Here are a few things to keep in mind:

  • Lagging Indicator – Since it relies on historical data, RRG may not always predict sudden market reversals.
  • Works Best for Groups – It is most effective when comparing multiple securities rather than analyzing a single stock.
  • Needs Confirmation – Combining RRG with other technical indicators like moving averages, RSI, or MACD can improve accuracy.
Conclusion

Relative Rotation Graphs provide a dynamic and insightful way to analyze market trends and sector rotation. By understanding the four quadrants and monitoring rotation patterns, traders and investors can make better-informed decisions about which stocks or sectors to buy, hold, or avoid.

Whether you’re a short-term trader or a long-term investor, integrating RRG into your strategy can offer a big-picture view of market trends, helping you stay ahead of the curve.


Support this community : FabTrader.in is a one-person initiative dedicated to helping individuals on their F.I.R.E. journey. Running and maintaining this community takes time, effort, and resources. If you’ve found value in the content, consider making a donation to support this mission.

Donate

Disclaimer: The information provided in this article is for educational and informational purposes only and should not be construed as financial, investment, or legal advice. The content is based on publicly available information and personal opinions and may not be suitable for all investors. Investing involves risks, including the loss of principal. Always conduct your own research and consult a qualified financial advisor before making any investment decisions. The author and website assume no liability for any financial losses or decisions made based on the information presented.

FabTrader

Vivek is an algorithmic trader, Python programmer, and a passionate advocate of the F.I.R.E. (Financial Independence, Retire Early) movement. He achieved his financial independence at the age of 45 and is dedicated to helping others embark on their own journeys toward financial freedom.

Home
Store
Stories
Algo Trading Platform Using Python Course
About Me

©2024 Fabtrader.in - An unit of Rough Sketch Company. All Rights Reserved

Terms & Conditions
Privacy Policy