Find NIFTY 500 Top Losers in the First 10 Minutes with Python (and Why Trading Them Didn't Work)
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:
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:
So what is the screener good for?
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.
Related Posts
Built something similar or want to talk through the architecture? Get in touch.