Are your equity investments actually beating Fixed Deposits?
Most retail investors park their money in Fixed Deposits earning 6-7% annually, assuming it’s the “safe” choice. But with inflation eating into returns, FDs often deliver negative real returns. The NIFTY 50 Rolling CAGR Heatmap helps you visualize which stocks have consistently outperformed FD returns over 1, 3, and 5-year periods.

What is Rolling CAGR?
CAGR (Compound Annual Growth Rate) measures the annualized return of an investment over a specified period. Unlike absolute returns, CAGR accounts for compounding and provides a standardized way to compare investments across different time horizons.
Formula:
CAGR = (End Value / Start Value)^(1/Years) – 1
A “rolling” CAGR calculates this metric continuously, giving you the annualized return from any point in time to the present day.

Why Heatmaps for Stock Analysis?
Heatmaps transform complex numerical data into intuitive visual patterns. At a glance, you can identify:
- Green zones: Strong performers with high CAGR
- Yellow zones: Moderate performers around FD-equivalent returns
- Red zones: Underperformers that failed to beat inflation
This visual approach eliminates the need to scan through spreadsheets and instantly highlights where the real wealth creators are.

Benchmarking Against FD Returns
Here’s the critical question every investor should ask: Is my stock portfolio beating what I’d earn in a Fixed Deposit?
Current FD rates hover around 6-7% annually. Use this as your benchmark:
| CAGR Range | Interpretation |
|---|---|
| > 15% | Excellent – Strong wealth creation |
| 10-15% | Good – Beating inflation comfortably |
| 7-10% | Average – Marginally better than FDs |
| < 7% | Poor – FD would have been better |
When you look at the heatmap:
- Stocks in deep green (>15% CAGR) are your wealth creators
- Stocks in yellow/orange (7-10%) barely justify the equity risk
- Stocks in red (<7%) have destroyed opportunity cost – FDs were the better choice
How to Use the CAGR Heatmap
For Long-Term Investors
- Identify Consistent Performers: Look for stocks that are green across 1Y, 3Y, and 5Y heatmaps. Consistency matters more than one exceptional year.
- Spot Turnaround Candidates: A stock red in 5Y but green in 1Y might indicate a turnaround – worth investigating further.
- Avoid Value Traps: Stocks red across all timeframes despite being “cheap” might be value traps. The market is often right.
For Portfolio Review
- Benchmark Your Holdings: Check where your current holdings fall on the heatmap. If most are in red/yellow zones, consider rebalancing.
- Sector Rotation Insights: Notice which sectors dominate the green zones. This reveals where long-term money is being made.
- Risk Assessment: A portfolio concentrated in red-zone stocks carries higher risk of underperformance.
Key Insights from NIFTY 50 CAGR Analysis
Analyzing the NIFTY 50 constituents over rolling periods reveals interesting patterns:
- Defensive sectors (FMCG, IT) often show moderate but consistent CAGRs
- Cyclical stocks (Metals, Infra) can swing from deep red to bright green
- New-age stocks may show N/A for 5Y CAGR due to recent listing
- PSU stocks have shown remarkable turnarounds in recent years
The FD Investor’s Dilemma
Consider this scenario:
- FD Investment: ₹10 lakhs at 7% for 5 years = ₹14.02 lakhs
- Stock with 15% CAGR: ₹10 lakhs for 5 years = ₹20.11 lakhs
- Difference: ₹6.09 lakhs (43% more wealth)
But here’s the catch – not all stocks deliver 15% CAGR. The heatmap helps you identify which ones historically have, reducing the guesswork in stock selection.
From the output above:
Top Performers (5Y CAGR):
- BEL: 59.89% – Defence sector rally, massive wealth creation
- MAXHEALTH: 50.11% – Healthcare sector strength
- TRENT: 45.47% – Retail consumption growth
Underperformers (5Y CAGR):
- HINDUNILVR: 1.79% – Below FD returns despite being a “safe” FMCG stock
- ASIANPAINT: 2.60% – Premium valuation compressed returns
- KOTAKBANK: 3.52% – Banking sector challenges
Key Insight: HINDUNILVR, often considered a “safe blue-chip,” delivered only 1.79% CAGR over 5 years – significantly worse than a 7% FD. This highlights why benchmarking matters.
Identifying Investment Patterns
Pattern 1: Consistent Performers Look for stocks green across all three timeframes (1Y, 3Y, 5Y):
- SHRIRAMFIN, BEL, BHARTIARTL, M&M
Pattern 2: Recent Turnarounds Green in 1Y but yellow/red in 5Y indicates recent momentum:
- MARUTI (57% 1Y, 16.8% 5Y)
- HINDALCO (54% 1Y, 29.65% 5Y)
Pattern 3: Fading Stars Red in 1Y but green in 5Y suggests weakening momentum:
- TRENT (-36.59% 1Y, 45.47% 5Y)
- ITC (-26.61% 1Y, 12.05% 5Y)
Pattern 4: New Entrants N/A in 3Y or 5Y indicates recently listed stocks:
- JIOFIN (only 1Y data available)
- ETERNAL (only 1Y and 3Y data)
Practical Use Cases
Portfolio Review
- List your current holdings
- Check their position on the heatmap
- If majority are in red/yellow zones, consider rebalancing
Stock Selection
- Focus on stocks in the green zone across multiple timeframes
- Avoid stocks consistently in red zones
- Research yellow-zone stocks for potential entry points
Sector Analysis
Group stocks by sector and observe patterns:
- IT Sector (TCS, INFY, WIPRO): Mostly yellow/red in 1Y
- PSU/Defence (BEL, NTPC, COALINDIA): Strong green across timeframes
- Auto (M&M, MARUTI, EICHERMOT): Recent strong performance
Technical Implementation
The heatmap is built using Python and OpenAlgo, fetching historical data directly from your broker.
# ---------------------------------------------------
# Python Code to Compute Rolling CAGR Heatmap for NIFTY 50
# Recommended to use Daily Historical Data more than 5 Years
# Minor variations in Rolling Returns might occur due to data source differences
# Coded by Rajandran R - Creator of OpenAlgo (https://openalgo.in)
# Author - www.marketcalls.in
# ---------------------------------------------------
# NOTE: This code requires OpenAlgo to be running locally or on a server.
# Get your API key from your self-hosted OpenAlgo platform.
# OpenAlgo GitHub: https://github.com/marketcalls/openalgo
# ---------------------------------------------------
from openalgo import api
import pandas as pd
import numpy as np
import plotly.express as px
from datetime import datetime, timedelta
# ---------------------------------------------------
# Initialize OpenAlgo Client
# ---------------------------------------------------
client = api(
api_key="your_api_key_here",
host="http://127.0.0.1:5000"
)
print("🔁 OpenAlgo Python Bot is running.")
# ---------------------------------------------------
# NIFTY 50 SYMBOLS
# ---------------------------------------------------
symbols = [
"INDIGO", "TRENT", "HINDUNILVR", "HCLTECH", "WIPRO", "INFY", "TATACONSUM",
"TATASTEEL", "ITC", "ASIANPAINT", "SBILIFE", "LT", "SHRIRAMFIN", "BEL", "SBIN",
"COALINDIA", "KOTAKBANK", "TCS", "SUNPHARMA", "MAXHEALTH", "NESTLEIND",
"RELIANCE", "ETERNAL", "APOLLOHOSP", "ICICIBANK", "GRASIM", "ULTRACEMCO",
"ADANIENT", "AXISBANK", "DRREDDY", "TECHM", "TMPV", "JIOFIN", "NTPC",
"BAJFINANCE", "BHARTIARTL", "POWERGRID", "HINDALCO", "HDFCBANK", "TITAN",
"HDFCLIFE", "MARUTI", "BAJAJFINSV", "ADANIPORTS", "CIPLA", "JSWSTEEL",
"BAJAJ-AUTO", "ONGC", "EICHERMOT", "M&M"
]
TRADING_DAYS_PER_YEAR = 252
# ---------------------------------------------------
# CAGR Calculation (matching TradingView logic)
# ---------------------------------------------------
def calc_cagr(start_price, end_price, years):
if pd.isna(start_price) or pd.isna(end_price) or start_price <= 0 or end_price <= 0:
return np.nan
return ((end_price / start_price) ** (1 / years) - 1) * 100
def get_price_by_trading_days(df, bars_back):
"""Get price exactly N trading bars back (like TradingView)"""
if len(df) <= bars_back:
return np.nan
return df["close"].iloc[-(bars_back + 1)]
# ---------------------------------------------------
# Fetch Historical Data & Calculate CAGRs
# ---------------------------------------------------
results = []
for symbol in symbols:
try:
end_date = datetime.now()
start_date = end_date - timedelta(days=365 * 6) # Extra buffer
df = client.history(
symbol=symbol,
exchange="NSE",
interval="D",
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d")
)
if not isinstance(df, pd.DataFrame) or df.empty:
print(f"{symbol}: No data received")
results.append([symbol, np.nan, np.nan, np.nan])
continue
df.index = pd.to_datetime(df.index)
df = df.sort_index()
price_now = df["close"].iloc[-1]
total_bars = len(df)
if total_bars < TRADING_DAYS_PER_YEAR:
print(f"{symbol}: Insufficient data ({total_bars} bars)")
results.append([symbol, np.nan, np.nan, np.nan])
continue
# Get prices using trading days (like TradingView)
bars_1y = TRADING_DAYS_PER_YEAR
bars_3y = TRADING_DAYS_PER_YEAR * 3
bars_5y = TRADING_DAYS_PER_YEAR * 5
price_1y = get_price_by_trading_days(df, bars_1y)
price_3y = get_price_by_trading_days(df, bars_3y)
price_5y = get_price_by_trading_days(df, bars_5y)
# Calculate returns
abs_1y = ((price_now / price_1y) - 1) * 100 if not pd.isna(price_1y) else np.nan
cagr_3y = calc_cagr(price_3y, price_now, 3)
cagr_5y = calc_cagr(price_5y, price_now, 5)
# Display status
abs_1y_str = f"{abs_1y:7.2f}%" if not pd.isna(abs_1y) else "N/A"
cagr_3y_str = f"{cagr_3y:7.2f}%" if not pd.isna(cagr_3y) else "N/A"
cagr_5y_str = f"{cagr_5y:7.2f}%" if not pd.isna(cagr_5y) else "N/A"
print(f"{symbol:12s} | 1Y: {abs_1y_str:>8s} | 3Y: {cagr_3y_str:>8s} | 5Y: {cagr_5y_str:>8s}")
results.append([symbol, abs_1y, cagr_3y, cagr_5y])
except Exception as e:
print(f"Error fetching {symbol}: {e}")
results.append([symbol, np.nan, np.nan, np.nan])
# ---------------------------------------------------
# Create DataFrame
# ---------------------------------------------------
df_cagr = pd.DataFrame(results, columns=["Symbol", "1Y", "3Y", "5Y"])
# ---------------------------------------------------
# Function to Create Heatmap
# ---------------------------------------------------
def create_heatmap(df, period, label):
df_period = df[["Symbol", period]].copy()
# Sort by value, putting NaN at the bottom
df_period = df_period.sort_values(period, ascending=False, na_position='last').reset_index(drop=True)
if df_period.empty:
print(f"⚠️ No data for {label}")
return
cols = 10
df_period["row"] = df_period.index // cols
df_period["col"] = df_period.index % cols
# Create display text: "SYMBOL\nValue%" or "SYMBOL\nN/A"
df_period["display_text"] = df_period.apply(
lambda row: f"{row['Symbol']}<br>{row[period]:.2f}%" if pd.notna(row[period]) else f"{row['Symbol']}<br>N/A",
axis=1
)
pivot_values = df_period.pivot(index="row", columns="col", values=period)
pivot_labels = df_period.pivot(index="row", columns="col", values="display_text")
fig = px.imshow(
pivot_values,
color_continuous_scale="RdYlGn",
aspect="auto"
)
fig.update_traces(
text=pivot_labels.values,
texttemplate="%{text}",
hovertemplate="%{text}<extra></extra>"
)
fig.update_layout(
title=f"NIFTY 50 {label} Heatmap (%)",
xaxis=dict(showticklabels=False, title=""),
yaxis=dict(showticklabels=False, autorange="reversed", title=""),
template="plotly_dark",
height=600,
width=1200
)
filename = f"nifty50_{period.lower()}_heatmap.png"
fig.write_image(filename, width=1200, height=600, scale=2)
print(f"✅ {label} Heatmap saved as {filename}")
# ---------------------------------------------------
# Generate Heatmaps
# ---------------------------------------------------
create_heatmap(df_cagr, "1Y", "1-Year Absolute Return")
create_heatmap(df_cagr, "3Y", "3-Year CAGR")
create_heatmap(df_cagr, "5Y", "5-Year CAGR")
print("\n✅ All heatmaps generated successfully!")
Key Features:
- Trading Days Based: Uses 252 trading days/year for accurate calculations
- Multiple Timeframes: 1-Year absolute returns, 3-Year and 5-Year CAGR
- Real-Time Data: Fetches live data from your connected broker via OpenAlgo
- Visual Export: Generates publication-ready PNG heatmaps
Getting Started
To generate your own CAGR heatmaps:
- Setup OpenAlgo: Install and configure OpenAlgo with your broker
- Get API Key: Obtain your API key from the OpenAlgo dashboard
- Run the Script: Execute the Python script to generate heatmaps
- Analyze: Use the heatmaps to make informed investment decisions
Conclusion
The NIFTY 50 Rolling CAGR Heatmap is a powerful tool for visualizing long-term stock performance. By benchmarking against FD returns, you can quickly identify which stocks justify the equity risk and which ones are better replaced with safer alternatives.
Remember: Time in the market beats timing the market – but only if you’re in the right stocks. Let the heatmap guide your selection.
Download the complete Python code from the link below and start generating your own CAGR heatmaps today.
Code Requirements:
- Python 3.10+
- OpenAlgo (self-hosted)
- Libraries: pandas, numpy, plotly