Build Your First Stock Scanner with AI | Zero Coding to Algo Trading

HI @Everyone,

Every algo trader eventually asks the same question: “How do I stop checking charts one by one and let the system find opportunities for me?” That’s exactly what a scanner does — it looks across your entire watchlist, applies your entry rules automatically, and tells you exactly which stocks are worth your attention right now.

In this part of the Complete Algo Series, we build a fully working momentum scanner using the Dhan API — starting from a blank file and ending with a scanner that checks RSI, EMA trend, and price gap across an entire watchlist, without you writing every line of code by hand.

Series so far: In the earlier parts, we set up the Dhan API connection, installed the required tools, and covered the core Python concepts needed for algo trading. If you haven’t gone through those yet, it’s worth doing so before this one — everything here builds directly on that foundation.


What You’ll Build

By the end of this tutorial, you’ll have a scanner that automatically flags stocks meeting three stacked conditions:

Condition Rule Purpose
Momentum RSI (14) > 60 for buy, < 40 for sell Confirms strength behind the move
Trend Close > EMA(30) for buy, < EMA(30) for sell Confirms the stock is trending, not chopping
Price Action Gap > 0.5% up (buy) or > 0.5% down (sell) Confirms fresh momentum at the day’s open

This same structure — define condition, code condition, test condition, stack conditions — is exactly how you’d build a scanner for any strategy: Fibonacci levels, Supertrend, candlestick patterns like Doji or Engulfing, or your own custom price-action setup.


scanner_workflow_diagram

Figure 1: The seven-step logic flow every scanner follows — from login to signal.


Why Scanners Matter in Algo Trading

Manually, scanning a watchlist means opening each stock’s chart one at a time, checking the indicator values, and deciding if it qualifies. For a 50-stock watchlist, that’s slow, repetitive, and error-prone — especially intraday, when conditions change every few minutes.

A coded scanner does the identical checks, just automatically and consistently:

  • Speed: Scans an entire watchlist (e.g., all Nifty 50 stocks) in seconds.
  • Consistency: Applies the exact same rule to every stock — no fatigue, no bias.
  • Testability: You can run it in “print-only” mode to confirm the logic before it ever places a real order.
  • Reusability: Once you know how to code one condition, you can code any condition — RSI, EMA, candlestick patterns, statistical measures like beta or correlation, and more.

:light_bulb: Tip: You do not need to be a professional programmer to do this. If you know what your entry condition should be in plain English, an AI coding assistant can translate that into working Python — your job is to verify the logic, not memorize syntax.


Step 1: Log In and Load the Watchlist

Every scanner session starts the same way a manual trading session does — you log in, and you pull up your watchlist.

Instead of writing this from scratch, reuse the login and watchlist code from your existing Dhan API codebase file (built in the earlier parts of this series). Copy that block into your new “Momentum Scanner” script and run it in debug mode to confirm the session connects successfully.

Note: Running in debug mode (rather than a full run) while building lets you step through the logic one line at a time and catch mistakes early — switch to a normal run only once the scanner is complete.

Once logged in, the watchlist — your list of stocks to scan — is loaded exactly as it was in earlier parts of the series.


Step 2: Loop Through the Watchlist

To check every stock one by one, wrap the scan logic in a for loop over your watchlist. This is the same looping concept covered in the Python fundamentals part of this series — the loop simply repeats the same block of code for each stock name in turn.

for name in watchlist:
    # scanning logic goes here, once per stock
    pass

:warning: Warning — Indentation Errors: Python relies on consistent indentation, not brackets, to define code blocks. If you see an error like unindent does not match any outer indentation level, select the affected lines, then convert indentation to tabs (or spaces) consistently throughout the file. Mixing tabs and spaces is the most common cause of this error.


Step 3: Fetch Historical (Candle) Data

Manually, checking a stock means opening its chart. In code, “opening a chart” means fetching historical OHLC (Open-High-Low-Close) candle data for that symbol.

Reuse the get historical data function from your codebase file, and call it inside the loop with three parameters:

  • Trading symbol — the current stock name from the loop
  • Exchange — e.g., NSE
  • Timeframe — e.g., 5-minute candles
for name in watchlist:
    chart = get_historical_data(
        trading_symbol=name,
        exchange="NSE",
        timeframe="5"
    )

Print chart inside the loop and confirm real candle data is returned for each stock before moving forward.


Step 4: Add the RSI Indicator

With candle data flowing in, the next step is calculating RSI — the first momentum condition. Rather than coding the RSI formula manually, use the TA-Lib library, which ships with a large set of ready-made indicators.

import talib as ta

rsi = ta.RSI(chart['close'], timeperiod=14)

:books: Reference: TA-Lib covers a wide range of tools beyond RSI — Bollinger Bands, EMA, SMA, WMA, CCI, Rate of Change, candlestick pattern recognition (Doji, Hammer, Inverted Hammer, Engulfing, and more), and even statistical functions like beta and correlation for quant-style strategies. Whatever indicator your strategy needs, it’s worth checking the library before writing it by hand.

Running Candle vs. Completed Candle

Before writing the entry condition, decide which candle you’re checking:

  • Running candle (chart.iloc[-1]) — the candle currently forming. Its values keep changing until the candle closes.
  • Completed candle (chart.iloc[-2]) — the most recently closed candle. Its values are final and won’t change.

Most scanning logic is built on the completed candle, since its RSI and price values are stable and won’t repaint mid-candle.

chart_bel_rsi_scan

Figure 2: Bharat Electronics (BEL) — RSI plotted beneath the price candles. At this stage, the scanner is checking RSI alone against the completed candle.


Step 5: Write the First Entry Condition (RSI Only)

With RSI available on the completed candle, the first version of the buy/sell condition is straightforward:

completed_rsi = rsi.iloc[-2]

buy_condition = completed_rsi > 60
sell_condition = completed_rsi < 40

if buy_condition:
    print(f"{name}: BUY signal — RSI {completed_rsi:.1f}")
    # place buy order + set stop-loss here (later step)

if sell_condition:
    print(f"{name}: SELL signal — RSI {completed_rsi:.1f}")
    # place sell order + set stop-loss here (later step)

Run this across the full watchlist with only print statements (no real orders) and confirm the scanner correctly flags stocks — for example, printing a BUY signal wherever RSI is genuinely above 60 on the completed candle.

:white_check_mark: Checkpoint: At this point you have a complete, working — if basic — scanner. It logs in, loops through every stock, pulls live candle data, calculates RSI, and prints a signal wherever the condition is met. Everything after this just adds more precision.


Step 6: Narrow the Signal with an EMA Trend Filter

An RSI-only scan on a full watchlist tends to return a lot of hits — RSI alone doesn’t confirm whether the stock is actually trending. Adding a trend filter (EMA) narrows the list to stocks that are both trending and showing momentum.

ema30 = ta.EMA(chart['close'], timeperiod=30)
completed_close = chart['close'].iloc[-2]
completed_ema = ema30.iloc[-2]

bullish_momentum = completed_rsi > 60
uptrend = completed_close > completed_ema

bearish_momentum = completed_rsi < 40
downtrend = completed_close < completed_ema

if bullish_momentum and uptrend:
    print(f"{name}: BUY signal — RSI {completed_rsi:.1f}, Close above EMA30")

if bearish_momentum and downtrend:
    print(f"{name}: SELL signal — RSI {completed_rsi:.1f}, Close below EMA30")

The logic is:

  • Bullish momentum = completed-candle RSI is above 60
  • Uptrend = completed-candle close is above its EMA(30)
  • A BUY signal only fires when both are true together

Verifying Against Real Charts

Running this combined scan can return signals like the ones below — always worth spot-checking manually before trusting the logic.

chart_tataconsumer_buy_signal

Figure 3: Tata Consumer Products — price closing above EMA(30) with RSI sustained above 60, satisfying both the trend and momentum conditions.

chart_reliance_buy_signal

Figure 4: Reliance Industries — same combined condition confirmed: close above EMA(30) and RSI comfortably above 60.

:magnifying_glass_tilted_left: Analogy: Think of RSI as the accelerator — it tells you how much force is behind the move. EMA is the road — it tells you the direction traffic is actually heading. You want both pointing the same way before you commit.


Step 7: Add a Price Gap Filter

The final layer adds a gap condition — checking how far today’s opening price has moved from yesterday’s closing price. This filters for stocks with genuinely fresh momentum at the day’s open, rather than a stock that has been quietly trending for days.

Fetch OHLC data and pull out the two reference points needed:

ohlc = get_ohlc_data(name)

todays_open = ohlc[name]['ohlc']['open']
yesterdays_close = ohlc[name]['ohlc']['close']

gap_pct = ((todays_open - yesterdays_close) / yesterdays_close) * 100

gapped_up = gap_pct > 0.5
gapped_down = gap_pct < -0.5

Note: Inside the OHLC response, open always refers to today’s opening price, close refers to the previous day’s closing price, while high and low update continuously through the live session to reflect the highest and lowest prices reached so far.

gap_condition_illustration

Figure 5: A gap-up is measured as the percentage difference between today’s open and yesterday’s close — here, a +0.90% gap clears the 0.5% threshold.


Step 8: Combine All Three Conditions

With RSI, EMA trend, and gap all coded individually, the final scanner simply combines them:

buy_signal = bullish_momentum and uptrend and gapped_up
sell_signal = bearish_momentum and downtrend and gapped_down

if buy_signal:
    print(f"{name}: BUY — RSI {completed_rsi:.1f} | Close > EMA30 | Gap {gap_pct:+.2f}%")

if sell_signal:
    print(f"{name}: SELL — RSI {completed_rsi:.1f} | Close < EMA30 | Gap {gap_pct:+.2f}%")

Run the full scanner across the complete watchlist. At this stage it is still print-only — no live orders are placed — which means you can validate the logic safely before ever risking capital.

Full Condition Summary

# Layer Buy Rule Sell Rule
1 Momentum RSI (14) > 60 RSI (14) < 40
2 Trend Close > EMA(30) Close < EMA(30)
3 Price Action Gap > +0.5% Gap < −0.5%

:warning: Warning: This scanner only prints signals — it does not place orders or manage stop-loss/target. Order placement and deployment are the next stage of this series, built deliberately as a separate, careful step so that execution logic is never rushed.


Building Your Own Conditions

The exact three conditions used here are just one example. The same process — define the rule , translate it with TA-Lib and an AI coding assistant, verify it on the completed candle, test in print-only mode — works for virtually any strategy:

  • Doji, Hammer, or Engulfing candlestick pattern scans
  • Bollinger Band breakout or squeeze scans
  • Supertrend flip scans
  • Statistical scans using correlation or beta, for quant-style approaches

If you get stuck translating a condition into code, describe the entry rule in plain language to an AI coding assistant and ask it to generate the corresponding Python — then verify the output the same way you verified RSI and EMA above, by cross-checking against a real chart.


Key Takeaways

  • A scanner is just your manual chart-checking process, automated and applied consistently across a whole watchlist.
  • Build conditions one at a time — get RSI working alone first, confirm it, then layer in EMA, then the gap filter.
  • Always work off the completed candle, not the running one, so your values don’t shift mid-check.
  • Test in print-only mode before any order placement logic is added — this is how you validate that the scanner “thinks” correctly.
  • The same pattern (indicator → condition → test → combine) applies to any strategy you want to automate, not just RSI/EMA momentum.

What’s Next

The scanner is now complete — it logs in, loops the watchlist, pulls live data, calculates indicators, and prints a signal exactly where your conditions are met. The next stage in the series covers order placement and deployment — turning these print statements into live (or paper) orders and running the scanner continuously in production.


Resources