TickAtlas
Tutorial 9 min read · March 28, 2026

Building a Market Scanner: Find Oversold Forex Pairs Instantly

Build a Python market scanner that checks RSI, Bollinger Bands, and Stochastic readings across all major pairs to find oversold opportunities in seconds.

CG
By the TickAtlas team

What is a Market Scanner?

Instead of manually checking indicators on each pair, a market scanner checks all pairs at once and surfaces only the ones that meet your criteria. It is the difference between flipping through 20 charts and getting a ranked list of the best opportunities in 3 seconds.

The Scanner Core

python
import requests
from dataclasses import dataclass

API_KEY = "your_api_key_here"
BASE_URL = "https://tickatlas.com/v1"
HEADERS = {"X-API-Key": API_KEY}

ALL_PAIRS = [
    "EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCHF",
    "USDCAD", "NZDUSD", "EURGBP", "EURJPY", "GBPJPY",
    "AUDJPY", "XAUUSD", "BTCUSD", "ETHUSD"
]

@dataclass
class ScanResult:
    symbol: str
    score: int  # 0-100, higher = more oversold
    rsi: float
    bb_position: str
    stoch_k: float
    price: float

def scan_oversold(timeframe: str = "H1") -> list[ScanResult]:
    """Scan all pairs for oversold conditions."""
    results = []

    for symbol in ALL_PAIRS:
        resp = requests.get(f"{BASE_URL}/indicators", params={
            "symbol": symbol,
            "timeframe": timeframe,
            "indicators": "RSI_14,BBANDS_20,STOCH_14_3_3"
        }, headers=HEADERS)

        if resp.status_code != 200:
            continue

        data = resp.json()["data"]
        ind = data["indicators"]
        price = data.get("price", {}).get("close", 0)

        rsi = ind["RSI_14"]["value"]
        bb = ind["BBANDS_20"]
        stoch = ind["STOCH_14_3_3"]["k_value"]

        # Calculate oversold score (0-100)
        score = 0

        # RSI contribution (max 40 points)
        if rsi < 20:
            score += 40
        elif rsi < 30:
            score += 30
        elif rsi < 40:
            score += 15

        # Bollinger Band contribution (max 30 points)
        if price <= bb["lower"]:
            score += 30
        elif price <= bb["lower"] * 1.005:
            score += 20
        elif price < bb["middle"]:
            score += 5

        # Stochastic contribution (max 30 points)
        if stoch < 10:
            score += 30
        elif stoch < 20:
            score += 25
        elif stoch < 30:
            score += 15

        bb_pos = "below" if price <= bb["lower"] else \
                 "near_lower" if price < bb["middle"] else "middle"

        results.append(ScanResult(
            symbol=symbol,
            score=score,
            rsi=rsi,
            bb_position=bb_pos,
            stoch_k=stoch,
            price=price
        ))

    # Sort by oversold score (highest first)
    results.sort(key=lambda r: r.score, reverse=True)
    return results

Running the Scanner

python
def print_scan_results(results: list[ScanResult],
                       min_score: int = 30):
    """Display scanner results in a formatted table."""
    filtered = [r for r in results if r.score >= min_score]

    if not filtered:
        print("No oversold pairs found.")
        return

    print(f"\n{'Symbol':<10} {'Score':>5} {'RSI':>6} {'Stoch':>6} {'BB Position':<12}")
    print("-" * 50)

    for r in filtered:
        print(f"{r.symbol:<10} {r.score:>5} {r.rsi:>6.1f} "
              f"{r.stoch_k:>6.1f} {r.bb_position:<12}")

# Scan and display
results = scan_oversold("H1")
print_scan_results(results)

# Example output:
# Symbol     Score    RSI  Stoch BB Position
# --------------------------------------------------
# AUDJPY        75   24.1   12.3 below
# NZDUSD        60   28.7   18.5 near_lower
# GBPJPY        45   33.2   22.1 near_lower

Multi-Timeframe Scan

python
def multi_timeframe_scan() -> list[dict]:
    """Find pairs that are oversold on multiple timeframes."""
    timeframes = ["H1", "H4", "D1"]
    pair_scores = {}

    for tf in timeframes:
        results = scan_oversold(tf)
        for r in results:
            if r.symbol not in pair_scores:
                pair_scores[r.symbol] = {"scores": {}, "details": {}}
            pair_scores[r.symbol]["scores"][tf] = r.score
            pair_scores[r.symbol]["details"][tf] = {
                "rsi": r.rsi, "stoch": r.stoch_k
            }

    # Filter: must be oversold on at least 2 timeframes
    confirmed = []
    for symbol, data in pair_scores.items():
        oversold_tfs = [tf for tf, s in data["scores"].items() if s >= 40]
        if len(oversold_tfs) >= 2:
            confirmed.append({
                "symbol": symbol,
                "avg_score": sum(data["scores"].values()) / len(data["scores"]),
                "oversold_timeframes": oversold_tfs,
                "details": data["details"]
            })

    confirmed.sort(key=lambda x: x["avg_score"], reverse=True)
    return confirmed

# Find multi-timeframe oversold pairs
strong_signals = multi_timeframe_scan()
for sig in strong_signals:
    tfs = ", ".join(sig["oversold_timeframes"])
    print(f"{sig['symbol']}: oversold on {tfs} (avg score: {sig['avg_score']:.0f})")

Automated Scanning Schedule

python
import time
from datetime import datetime

def run_scanner_loop():
    """Run scanner on a schedule and alert on new signals."""
    previous_signals = set()

    while True:
        results = scan_oversold("H1")
        current_signals = set()

        for r in results:
            if r.score >= 50:  # Only high-confidence signals
                current_signals.add(r.symbol)

                if r.symbol not in previous_signals:
                    print(f"\n[{datetime.utcnow()}] NEW: {r.symbol} "
                          f"(score: {r.score}, RSI: {r.rsi:.1f})")
                    # send_telegram_alert(r)  # Optional

        # Report any pairs that recovered
        recovered = previous_signals - current_signals
        for symbol in recovered:
            print(f"[{datetime.utcnow()}] RECOVERED: {symbol} no longer oversold")

        previous_signals = current_signals
        time.sleep(300)  # Scan every 5 minutes

Extending the Scanner

Add Overbought Scanning

Mirror the logic: RSI > 70, price near upper Bollinger Band, Stochastic > 80. Same code structure, inverse conditions.

Currency Strength Filter

Combine with currency strength analysis to only buy oversold pairs where the base currency is fundamentally strong.

Telegram Integration

Send scan results to Telegram using the pattern from our Telegram bot tutorial.

Further Reading

Try this with live data

Every account gets $2.50 in free PAYG credits. No card required — paste your API key and run the code above against live broker data.