PythonNIFTY 500stock screeneralgo tradingIndiaExcel automation

Find NIFTY 500 Top Losers in the First 10 Minutes with Python (and Why Trading Them Didn't Work)

·8 min read

Right after 9:15 AM the market is noisy, and some stocks fall hard in the first few minutes. A common idea: find the biggest losers right after the open and trade them. I built a screener that does the finding part automatically: every weekday it scans all NIFTY 500 stocks and writes a sorted, colour-coded Excel sheet within minutes of the open.


Then I backtested trading the top movers for a year. That part didn't work, and why it didn't is the most useful thing in this post.


What the screener does


1. Downloads the current NIFTY 500 list from NSE

2. Takes a price for every stock a few minutes after the open (first snapshot)

3. Takes a second price about 10 minutes later

4. Calculates the change for each stock, sorts by the biggest fall, and writes one Excel sheet for the day, red rows for losers and green for gainers


I wait a few minutes after 9:15 before the first snapshot, because the first prints after the open are erratic. The two times live in config.json, so you can try different windows.


Step 1: get the NIFTY 500 list from NSE


NSE publishes the constituents as a CSV. It rejects requests without a browser-like User-Agent, and the URL occasionally fails, so keep a local copy as a fallback:



import os, requests, pandas as pd

URL = "https://nsearchives.nseindia.com/content/indices/ind_nifty500list.csv"

def nifty500_symbols():
    try:
        r = requests.get(URL, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
        r.raise_for_status()
        open("nifty500_latest.csv", "wb").write(r.content)
    except Exception as e:
        print("NSE fetch failed, using local copy:", e)
        if not os.path.exists("nifty500_latest.csv"):
            raise
    return pd.read_csv("nifty500_latest.csv")["Symbol"].tolist()

The index is rebalanced twice a year, so refresh the list rather than hard-coding it.


Step 2: fetch prices for 500 stocks


Free option: yfinance


Yahoo Finance uses the .NS suffix for NSE stocks. Fetching 500 one by one is slow, so run them in parallel with a small random delay to avoid being throttled:



import random, time, concurrent.futures
import yfinance as yf

def ltp(symbol):
    time.sleep(random.uniform(0.02, 0.08))
    data = yf.Ticker(symbol + ".NS").history(period="1d", interval="1m")
    return symbol, (float(data["Close"].iloc[-1]) if not data.empty else None)

def all_prices(symbols):
    with concurrent.futures.ThreadPoolExecutor(max_workers=15) as pool:
        return {s: p for s, p in pool.map(ltp, symbols) if p is not None}

All 500 take about 25–30 seconds. Expect a handful to fail each day (suspended stocks, symbol changes); log them and move on.


Caveats with Yahoo: it's unofficial, its NSE prices can lag the exchange, and it keeps only about a week of 1-minute history. It's fine for a screener. It's not what you want for placing orders.


Faster option: Kite Connect


If you have a Kite Connect subscription (₹500/month), one quote call accepts up to 500 instruments, so the whole index takes two calls, under a second each:



keys = [f"NSE:{s}" for s in symbols]
quotes = {}
for i in range(0, len(keys), 500):
    quotes.update(kite.quote(keys[i:i + 500]))
    time.sleep(1)          # Kite allows ~1 quote call per second

A quote response also includes the day's open, high, low and previous close, so you can rank by fall from the open or from yesterday's close without a second snapshot. Setup and login are covered in my Kite Connect bot guide.


Step 3: rank and write the Excel sheet



def rank(first, second):
    rows = []
    for s, p1 in first.items():
        p2 = second.get(s)
        if p2:
            rows.append({"Symbol": s, "LTP 1": p1, "LTP 2": p2,
                         "Change %": round((p2 - p1) / p1 * 100, 2)})
    return pd.DataFrame(rows).sort_values("Change %")   # biggest losers first

For the sheet I use openpyxl: one file per day (outputs/nifty500_daily_YYYYMMDD.xlsx), both snapshots side by side, a summary block at the top (stocks up vs down, average change), and red/green fills per row.



from openpyxl.styles import PatternFill
RED, GREEN = PatternFill("solid", fgColor="FFC7CE"), PatternFill("solid", fgColor="C6EFCE")

for row in ws.iter_rows(min_row=2):
    change = row[3].value
    for cell in row:
        cell.fill = RED if change < 0 else GREEN

I also simulate a paper portfolio: split ₹6,00,000 equally across the stocks, whole shares only, skipping any stock where one share costs more than its slice. That turns "% change" into a rupee P&L you can feel.


Step 4: run it every weekday on IST


The schedule library is enough on a laptop:



import schedule
schedule.every().monday.at("09:20").do(first_snapshot)
schedule.every().monday.at("09:30").do(second_snapshot)
# ...repeat for each weekday
while True:
    schedule.run_pending()
    time.sleep(5)

On a server, use a systemd timer or cron instead, and remember that most VPS clocks run on UTC (9:20 IST = 03:50 UTC). Also skip NSE holidays: keep a yearly holidays.yaml and check it before running, or you'll get a sheet full of yesterday's prices.


What happened when I tried trading the top movers


The screener answers "who fell the most?". The tempting next step is "so buy the dip" or "short the top gainers". So I backtested it properly:


  • 494 of the NIFTY 500 stocks, 247 trading days (October 2025 – October 2026), 1-minute candles
  • Pick the top movers at 10:00, enter, exit by 1:00 PM or at the stop
  • Zerodha charges and slippage included
  • Variants: long or short, top 3 / 5 / 10, stops of 2% / 3% / 4%

  • Result: no edge. Every variant lost money. And charges weren't the reason: the main version was already slightly negative before charges.


    The pattern that explained it: shorts won for about three months and lost for about five, and longs did the reverse. The rule wasn't finding anything about the stocks. It was just betting on the market's direction that month, and that flips.


    A short paper run confirmed it. Most picks dipped a little after entry and then kept moving the original way until 1:00 PM.


    Practical problems that would have made it worse live:


  • A stock at its 20% upper circuit may not be shortable at all
  • Some NIFTY 500 stocks aren't in F&O, and Zerodha may refuse an intraday (MIS) short on them
  • Paper fills assume you always get filled; real circuit-hit stocks are hard to exit

  • So what is the screener good for?


  • A watchlist, not a signal. It shows where the action is each morning, and you can dig into the why (results, news, block deals) before doing anything.
  • Market breadth at a glance. How many of 500 are red ten minutes in tells you more about the day than the NIFTY number.
  • Raw data for research. Saving every day's sheet builds your own dataset for testing better ideas.

  • If you want to test your own "top losers" idea, the lesson is: backtest across a full year that includes both rising and falling months, with charges, before you believe a good week. More on that in Is algo trading profitable in India?


    Want a custom screener or Excel report wired to your broker account? Get in touch.

    Built something similar or want to talk through the architecture? Get in touch.