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.
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
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
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
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
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.
Keep reading
All articles- Tutorial 11 min read
24/7 Crypto Monitoring: Building Always-On Analysis Systems
Build a monitoring system that watches crypto markets around the clock, detects significant moves, and sends alerts when conditions match your criteria.
March 28, 2026
- Tutorial 12 min read
How to Build an AI Market Analyst That Runs 24/7
Build a production-ready AI market analyst that monitors forex and crypto markets around the clock, generates daily briefings, and alerts you to opportunities via Telegram.
March 28, 2026
- Tutorial 10 min read
Using ATR for Dynamic Stop-Loss Placement
Learn how to use Average True Range (ATR) to set volatility-adjusted stop losses that adapt to market conditions, with full code examples.
March 28, 2026