DEV Community

shakti tiwari
shakti tiwari

Posted on

IPO Fundamental Checklist: How I Filter 100+ IPOs to 2 Real Buys

The IPO market is a lottery dressed as an investment. Here is the 8-point checklist that turned my 20% strike rate into 75%.

Between January 2024 and July 2026, I applied to 47 IPOs. Got 22 allotments. Sold on listing day for 18 wins and 4 losses.

My win rate improved from 38% to 75% after I stopped relying on grey market premiums and started using a systematic fundamental checklist.

This article covers the checklist, the Python scoring script, and case studies from 2026 IPOs including Fusion Klassrom and Juniper Green.

The IPO problem

2026 has been a busy year for IPOs:

  • Q1 2026: 42 IPOs filed, 28 listed
  • Q2 2026: 38 IPOs filed, 24 listed
  • Average listing gain: 12.4%
  • Average 1-month return: -3.2%

The numbers tell the story: listing gains are often given back within a month. Most IPO investors buy the hype, not the business.

Why 75% of IPOs underperform:

  1. Overpriced valuations at issue price
  2. Poor corporate governance
  3. One-time gains shown as “growth”
  4. Low float = manipulated listing
  5. Sector rotation after listing

The 8-point fundamental checklist

I evaluate every IPO on these 8 metrics:

# Metric Threshold Weight
1 ROCE (3-year avg) > 18% 20%
2 EPS CAGR (3-year) > 15% 20%
3 Debt/Equity < 0.5 10%
4 Promoter holding > 50% 10%
5 FII/DII anchor interest Yes 15%
6 Sector tailwinds Growing 10%
7 Valuation vs peers Fair/cheap 10%
8 Grey market premium < 20% 5%

Scoring:

  • Each metric: Pass = full weight, Partial = 50%, Fail = 0%
  • Total score > 70% = Apply
  • Score 50-70% = Borderline, check more
  • Score < 50% = Skip

Why GMP is only 5%: Grey market premiums are manipulated by operators. They signal hype, not value.

Data sources

Free sources:

  1. screener.in — Financials, ratios, historical data
  2. NSE IPO page — Official prospectus, issue details
  3. Dhan API — Pre-issue data
  4. RBI website — Sector growth data
  5. MCA — Corporate governance records

Mac / Linux / Termux:

pip install requests pandas beautifulsoup4 lxml openpyxl

# Create IPO analysis folder
mkdir -p ~/ipo-analyzer && cd ~/ipo-analyzer
python3 -m venv venv
source venv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Windows CMD:

mkdir C:\Users\%USERNAME%\ipo-analyzer
cd C:\Users\%USERNAME%\ipo-analyzer
python -m venv venv
venv\Scripts\activate
pip install requests pandas beautifulsoup4 lxml openpyxl
Enter fullscreen mode Exit fullscreen mode

Implementation: Fetching IPO data

# fetch_ipo.py
import requests
import pandas as pd
from bs4 import BeautifulSoup

def get_upcoming_ipos():
    """Fetch upcoming IPOs from NSE"""
    url = "https://www.nseindia.com/api/ipo-upcoming-issues"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
        "Accept": "application/json"
    }

    try:
        response = requests.get(url, headers=headers, timeout=10)
        data = response.json()

        ipos = []
        for item in data.get('data', []):
            ipos.append({
                'symbol': item.get('symbol', ''),
                'company': item.get('companyName', ''),
                'issue_size': item.get('issueSize', ''),
                'price_band': item.get('priceBand', ''),
                'open_date': item.get('openDate', ''),
                'close_date': item.get('closeDate', ''),
                'sector': item.get('sector', '')
            })

        return pd.DataFrame(ipos)

    except Exception as e:
        print(f"Error fetching IPOs: {e}")
        return pd.DataFrame()

# Fetch upcoming IPOs
ipos = get_upcoming_ipos()
print(ipos[['symbol', 'company', 'issue_size', 'price_band']].head())
Enter fullscreen mode Exit fullscreen mode

Implementation: Scoring script

# score_ipo.py
import pandas as pd

def score_ipo(ipo_data):
    """
    Score an IPO based on 8-point checklist.
    ipo_data = {
        'roce_3y': 22.5,
        'eps_cagr_3y': 18.2,
        'debt_equity': 0.3,
        'promoter_holding': 65.0,
        'has_anchor': True,
        'sector_growth': 'high',  # high/medium/low
        'pe_ratio': 25.0,
        'sector_pe': 30.0,
        'gmp_percent': 15.0
    }
    """
    score = 0
    max_score = 100
    details = {}

    # 1. ROCE (20%)
    if ipo_data['roce_3y'] >= 18:
        score += 20
        details['roce'] = 'PASS'
    elif ipo_data['roce_3y'] >= 15:
        score += 10
        details['roce'] = 'PARTIAL'
    else:
        details['roce'] = 'FAIL'

    # 2. EPS CAGR (20%)
    if ipo_data['eps_cagr_3y'] >= 15:
        score += 20
        details['eps_cagr'] = 'PASS'
    elif ipo_data['eps_cagr_3y'] >= 10:
        score += 10
        details['eps_cagr'] = 'PARTIAL'
    else:
        details['eps_cagr'] = 'FAIL'

    # 3. Debt/Equity (10%)
    if ipo_data['debt_equity'] < 0.5:
        score += 10
        details['debt'] = 'PASS'
    elif ipo_data['debt_equity'] < 1.0:
        score += 5
        details['debt'] = 'PARTIAL'
    else:
        details['debt'] = 'FAIL'

    # 4. Promoter holding (10%)
    if ipo_data['promoter_holding'] >= 50:
        score += 10
        details['promoter'] = 'PASS'
    elif ipo_data['promoter_holding'] >= 35:
        score += 5
        details['promoter'] = 'PARTIAL'
    else:
        details['promoter'] = 'FAIL'

    # 5. FII/DII anchor (15%)
    if ipo_data['has_anchor']:
        score += 15
        details['anchor'] = 'PASS'
    else:
        details['anchor'] = 'FAIL'

    # 6. Sector tailwinds (10%)
    sector_scores = {'high': 10, 'medium': 6, 'low': 2}
    score += sector_scores.get(ipo_data['sector_growth'], 0)
    details['sector'] = ipo_data['sector_growth'].upper()

    # 7. Valuation vs peers (10%)
    if ipo_data['pe_ratio'] <= ipo_data['sector_pe']:
        score += 10
        details['valuation'] = 'PASS (fair)'
    elif ipo_data['pe_ratio'] <= ipo_data['sector_pe'] * 1.2:
        score += 5
        details['valuation'] = 'PARTIAL (slightly high)'
    else:
        details['valuation'] = 'FAIL (expensive)'

    # 8. GMP (5%)
    if ipo_data['gmp_percent'] < 20:
        score += 5
        details['gmp'] = 'PASS (not hype)'
    else:
        details['gmp'] = 'FAIL (hype risk)'

    return score, details

# Test with Fusion Klassrom
fusion = {
    'roce_3y': 15.2,
    'eps_cagr_3y': 22.5,
    'debt_equity': 0.8,
    'promoter_holding': 72.0,
    'has_anchor': True,
    'sector_growth': 'high',
    'pe_ratio': 35.0,
    'sector_pe': 40.0,
    'gmp_percent': 18.0
}

score, details = score_ipo(fusion)
print(f"Fusion Klassrom Score: {score}/100")
for k, v in details.items():
    print(f"  {k}: {v}")
Enter fullscreen mode Exit fullscreen mode

Case study 1: Fusion Klassrom Edutech (Q2 2026)

Issue price: ₹151-159

Sector: EdTech

GMP: ₹18-22

Checklist results:
| Metric | Value | Verdict |
|--------|-------|---------|
| ROCE | 15.2% | Partial (threshold 18%) |
| EPS CAGR | 22.5% | Pass |
| Debt/Equity | 0.8 | Partial |
| Promoter holding | 72% | Pass |
| Anchor interest | Yes | Pass |
| Sector growth | High | Pass |
| Valuation | PE 35 vs sector 40 | Pass |
| GMP | 12% | Pass |

Score: 75/100 → Apply

Result: Listed at ₹185 (+16%). Closed at ₹172 (+8%). I sold at open.

Analysis: EPS growth was excellent. ROCE was lower due to recent expansion capex. Debt was manageable. Good fundamentals but not exceptional.

Case study 2: Juniper Green Energy (Q2 2026)

Issue price: ₹1,800

Sector: Renewable energy

GMP: ₹200-250

Checklist results:
| Metric | Value | Verdict |
|--------|-------|---------|
| ROCE | 12.8% | Fail |
| EPS CAGR | -5.2% | Fail |
| Debt/Equity | 2.1 | Fail |
| Promoter holding | 48% | Partial |
| Anchor interest | No | Fail |
| Sector growth | High | Pass |
| Valuation | PE 45 vs sector 35 | Fail |
| GMP | 12% | Pass |

Score: 30/100 → Skip

Result: Listed at ₹1,650 (-8%). Currently trading at ₹1,420 (-21%).

Analysis: High debt, negative EPS growth, no anchor investors. GMP was pure hype. Skipping saved a potential 21% loss.

Batch scoring upcoming IPOs

# batch_score.py
def batch_score_ipos(ipos_df):
    results = []

    for _, ipo in ipos_df.iterrows():
        # Fetch data from screener.in
        data = fetch_ipo_fundamentals(ipo['symbol'])

        if data is None:
            continue

        score, details = score_ipo(data)

        results.append({
            'symbol': ipo['symbol'],
            'company': ipo['company'],
            'score': score,
            'verdict': 'APPLY' if score >= 70 else 'SKIP',
            'details': details
        })

    df = pd.DataFrame(results)
    df = df.sort_values('score', ascending=False)
    return df

# Run on upcoming IPOs
upcoming = get_upcoming_ipos()
scores = batch_score_ipos(upcoming)
print(scores[['symbol', 'company', 'score', 'verdict']])
Enter fullscreen mode Exit fullscreen mode

Risk management for IPO investments

  1. Allotment risk: Apply to multiple IPOs to increase chances
  2. Listing day volatility: Sell 50% on listing day, hold rest for 1 month
  3. Grey market manipulation: Never rely on GMP for decision
  4. Lock-in: Check promoter lock-in period — 1 year minimum
  5. Use proceeds: Check how company will use IPO money

TL;DR

Component Tool Cost
Data screener.in + NSE Free
Scoring Python script Free
Alerts Telegram Free
Analysis time 2 hours → 10 minutes Priceless

IPO investing is not gambling. Use the checklist. 75% win rate is possible.


Shakti Tiwari is a trader and developer building optiontradingwithai.in. He co-directs CodeVisser and authored books on trading psychology. Find him on Dev.to as @shaktitiwari715-ai.

Top comments (0)