How I Built a Free Crypto Market Scanner That Spots High-Probability Trades (No Paid APIs, No Hype)
Short Description: Most “signal bots” are leaky wrappers around paid data feeds. This is different: a fully transparent Python system using…
How I Built a Free Crypto Market Scanner That Spots High-Probability Trades (No Paid APIs, No Hype)
Short Description: Most “signal bots” are leaky wrappers around paid data feeds. This is different: a fully transparent Python system using only Binance and CoinGecko’s free tiers. You’ll get the exact architecture, production-ready code, and the quantitative filters that actually separate noise from edge.
Article Roadmap: • Why 99% of free bots fail (and how to avoid the trap) • The exact API stack that costs $0 • Building the market scanner loop (fetch, filter, cache) • Signal engineering: momentum, volume, volatility, and regime filters • Full Python implementation (copy-paste ready) • Risk filters & paper trading mode • Deployment, rate limits, and survival rules
⏱️ Estimated reading time: 15–18 minutes

HOOK
I lost $1,200 in three weeks following a “premium” Telegram bot that promised institutional-grade signals. When I finally reverse-engineered its logic, it was just an RSI crossover on a 5-minute chart with zero volume confirmation, running against a 0.1% spread. That’s when I realized: the edge isn’t in the signal. It’s in the scanner.
I spent 14 days building a lightweight, free-API market scanner in Python. It doesn’t promise riches. It promises clarity. And after 10 months of running it in paper mode, it’s still my single most valuable trading tool. Here’s exactly how to build it yourself.
1. The Myth of “Free Signals” (And Why They Actually Work If You Build Them)
Retail traders chase signals. Professionals build scanners.
A signal is a snapshot: “Buy X now.” A scanner is a system: “Find assets where A, B, and C align under condition D, then output for review.” Signals decay the moment they’re shared. Scanners compound because they force you to define, test, and refine your edge.
Free APIs are more than enough for retail scanning. You don’t need WebSocket streams or historical tick data to spot high-probability setups. You need:
- Liquidity filters (to avoid illiquid traps)
- Confluence indicators (momentum + trend + volume)
- Market regime awareness (is BTC chopping, trending, or dumping?)
- Strict rate limit handling (free ≠ infinite)
Build this right, and you’ll outperform 90% of paid Discord groups.
2. Architecture of a Real Market Scanner
Before writing code, map the data flow. This is where most tutorials fail. They jump straight to indicators without explaining the pipeline.
CoinGecko (free tier) → Top coins by 24h volume & market cap
↓
Map to Binance trading pairs (USDT)
↓
Binance /api/v3/klines → 100x 1h candles per pair
↓
Indicator Engine → RSI(14), EMA(9/21), Volume MA(20)
↓
Signal Filter → Confluence rules + BTC regime check
↓
Output → Console / Log / Telegram webhook
Why CoinGecko first? Because it gives you a clean, ranked list of liquid assets without guessing which Binance pairs are worth scanning. Why 1-hour candles? They filter out noise while catching swing setups. Why 100 candles? Enough for stable moving averages without overloading free APIs.
3. Step 1: API Setup (Binance + CoinGecko)
You don’t need API keys for this. Both platforms expose public endpoints with generous free limits:
- CoinGecko:
GET https://api.coingecko.com/api/v3/coins/marketsFree tier: ~10–30 requests/minute. We’ll use?vs_currency=usd&order=market_cap_desc&per_page=50 - Binance:
GET https://api.binance.com/api/v3/klinesFree tier: 1,200 weight/minute.interval=1h&limit=100costs ~2 weight per pair.
Critical rule: Respect rate limits or get IP-banned. We’ll add built-in delays and exponential backoff.
4. Step 2: The Scanner Loop
The scanner must:
- Fetch top 50 coins by volume/market cap
- Map
id→ Binance symbol (bitcoin→BTCUSDT) - Pull OHLCV data
- Cache results to avoid redundant calls
- Handle timeouts, JSON errors, and missing pairs gracefully
We’ll use synchronous requests for readability. In production, aiohttp or async httpx would be faster, but async adds complexity that obscures the core logic. Start simple. Optimize later.
5. Step 3: Signal Engineering
Indicators don’t generate alpha. Confluence does.
Here’s the filter stack we’ll implement:
- Trend: Price > EMA(21)
- Momentum: RSI(14) < 38 (pullback in an uptrend)
- Volume: Current 1h volume > 1.8x 20-period average
- Regime Filter: BTC must not be in a sharp downtrend (BTC RSI(14) > 35 on 1h)
Why this combo? It catches continuation setups after healthy pullbacks. It avoids catching falling knives. It ignores low-volume pumps that reverse in 15 minutes.
6. Step 4: The Complete Python Implementation
Save this as market_scanner.py. Run with python market_scanner.py.
How to run it:
import requests
import time
import datetime
from datetime import timezone
# ─────────────────────────────────────────────────────────────
# CONFIGURATION
# ─────────────────────────────────────────────────────────────
COINGECKO_URL = "https://api.coingecko.com/api/v3/coins/markets"
BINANCE_URL = "https://api.binance.com/api/v3/klines"
SCAN_INTERVAL_SEC = 180 # 3 minutes (respects CoinGecko free tier)
MIN_24H_VOLUME_USD = 50_000_000
LOG_FILE = "signals.log"
# Mapping CoinGecko IDs to Binance symbols
COIN_MAPPING = {
'bitcoin': 'BTCUSDT',
'ethereum': 'ETHUSDT',
'binancecoin': 'BNBUSDT',
'solana': 'SOLUSDT',
'xrp': 'XRPUSDT',
'dogecoin': 'DOGEUSDT',
'toncoin': 'TONUSDT',
'cardano': 'ADAUSDT',
'avalanche-2': 'AVAXUSDT',
'shiba-inu': 'SHIBUSDT',
'chainlink': 'LINKUSDT',
'polkadot': 'DOTUSDT',
'bitcoin-cash': 'BCHUSDT',
'near': 'NEARUSDT',
'uniswap': 'UNIUSDT',
'litecoin': 'LTCUSDT',
'internet-computer': 'ICPUSDT',
'ethereum-classic': 'ETCUSDT',
'aptos': 'APTUSDT',
'stellar': 'XLMUSDT',
'filecoin': 'FILUSDT',
'cosmos': 'ATOMUSDT',
'hedera-hashgraph': 'HBARUSDT',
'cronos': 'CROUSDT',
'arbitrum': 'ARBUSDT',
'vechain': 'VETUSDT',
'monero': 'XMRUSDT',
'the-graph': 'GRTUSDT',
'fantom': 'FTMUSDT',
'theta-token': 'THETAUSDT',
'algorand': 'ALGOUSDT',
'flow': 'FLOWUSDT',
'elrond-erd-2': 'EGLDUSDT',
'aave': 'AAVEUSDT',
'eos': 'EOSUSDT',
'axie-infinity': 'AXSUSDT',
'tezos': 'XTZUSDT',
'sandbox': 'SANDUSDT',
'decentraland': 'MANAUSDT',
'bitcoin-sv': 'BSVUSDT',
'neo': 'NEOUSDT',
'maker': 'MKRUSDT',
'iota': 'IOTAUSDT',
'quant-network': 'QNTUSDT',
'chiliz': 'CHZUSDT',
'okb': 'OKBUSDT',
'kucoin-shares': 'KCSUSDT',
}
# ─────────────────────────────────────────────────────────────
# HELPER FUNCTIONS
# ─────────────────────────────────────────────────────────────
def safe_request(url, params, retries=3):
"""Make HTTP request with retry logic and rate limit handling"""
for attempt in range(retries):
try:
res = requests.get(url, params=params, timeout=10)
if res.status_code == 429:
wait = 30 * (attempt + 1)
print(f"[RATE LIMIT] Waiting {wait}s...")
time.sleep(wait)
continue
res.raise_for_status()
return res.json()
except Exception as e:
print(f"[REQUEST ERROR] {e}")
if attempt == retries - 1:
return None
time.sleep(5 * (attempt + 1))
return None
def calculate_rsi(closes, period=14):
"""Calculate Relative Strength Index"""
if len(closes) < period + 1:
return 50.0
gains = []
losses = []
for i in range(1, len(closes)):
diff = closes[i] - closes[i-1]
gains.append(max(diff, 0))
losses.append(max(-diff, 0))
if len(gains) < period:
return 50.0
avg_gain = sum(gains[:period]) / period
avg_loss = sum(losses[:period]) / period
for i in range(period, len(gains)):
avg_gain = (avg_gain * (period - 1) + gains[i]) / period
avg_loss = (avg_loss * (period - 1) + losses[i]) / period
if avg_loss == 0:
return 100
rs = avg_gain / avg_loss
return 100 - (100 / (1 + rs))
def calculate_ema(values, period):
"""Calculate Exponential Moving Average"""
if not values:
return 0
if len(values) < period:
return sum(values) / len(values)
k = 2 / (period + 1)
ema = values[0]
for price in values[1:]:
ema = price * k + ema * (1 - k)
return ema
def log_signal(symbol, price, rsi, vol_ratio, reason):
"""Log trading signal to console and file"""
ts = datetime.datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
entry = f"[{ts}] {symbol} | Price: ${price:.2f} | RSI: {rsi:.1f} | Vol: {vol_ratio:.2f}x | {reason}"
print(entry)
try:
with open(LOG_FILE, "a") as f:
f.write(entry + "\n")
except Exception as e:
print(f"[LOG ERROR] Could not write to log file: {e}")
# ─────────────────────────────────────────────────────────────
# SCANNER ENGINE
# ─────────────────────────────────────────────────────────────
def fetch_top_coins():
"""Fetch top coins from CoinGecko and map to Binance symbols"""
params = {
"vs_currency": "usd",
"order": "market_cap_desc",
"per_page": 100,
"page": 1,
"sparkline": "false"
}
data = safe_request(COINGECKO_URL, params)
if not data:
print("[ERROR] Failed to fetch data from CoinGecko")
return []
filtered_coins = []
for coin in data:
coin_id = coin.get("id", "")
# Skip stablecoins and non-mapped coins
if coin_id in ['tether', 'usd-coin', 'dai', 'busd', 'trueusd']:
continue
symbol = COIN_MAPPING.get(coin_id)
if not symbol:
continue
# Check volume and market cap filters
total_volume = coin.get("total_volume", 0)
market_cap_rank = coin.get("market_cap_rank", 999)
if total_volume >= MIN_24H_VOLUME_USD and market_cap_rank <= 50:
filtered_coins.append({
'id': coin_id,
'symbol': symbol,
'name': coin.get('name', ''),
'volume': total_volume,
'rank': market_cap_rank
})
return filtered_coins
def get_klines(symbol, interval="1h", limit=100):
"""Fetch candlestick data from Binance"""
params = {
"symbol": symbol,
"interval": interval,
"limit": limit
}
data = safe_request(BINANCE_URL, params)
if not data:
return None, None, None
# Binance klines format: [time, open, high, low, close, volume, ...]
closes = [float(k[4]) for k in data]
volumes = [float(k[5]) for k in data]
return closes, volumes, data
def scan_market():
"""Main scanning function"""
now = datetime.datetime.now(timezone.utc)
print(f"\n[{'='*50}]")
print(f"[SCANNER START] {now.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"[{'='*50}]\n")
# 1. Check BTC market regime
print("[INFO] Checking BTC market regime...")
btc_closes, _, _ = get_klines("BTCUSDT")
if not btc_closes:
print("[WARN] Failed to fetch BTC data. Skipping regime filter.")
btc_rsi = 50
else:
btc_rsi = calculate_rsi(btc_closes, 14)
print(f"[INFO] BTC RSI(14): {btc_rsi:.1f}")
if btc_rsi < 35:
print("[REGIME] BTC is oversold. Trading signals suppressed.")
return
elif btc_rsi > 70:
print("[REGIME] BTC is overbought. Use caution.")
# 2. Fetch top coins
print("[INFO] Fetching top coins from CoinGecko...")
coins = fetch_top_coins()
if not coins:
print("[ERROR] No coins fetched. Check your internet connection.")
return
print(f"[INFO] Found {len(coins)} coins to scan\n")
# 3. Scan each coin
signals_found = 0
for coin in coins:
symbol = coin['symbol']
try:
closes, volumes, _ = get_klines(symbol)
if not closes or len(closes) < 50:
continue
current_price = closes[-1]
rsi = calculate_rsi(closes, 14)
ema9 = calculate_ema(closes, 9)
ema21 = calculate_ema(closes, 21)
# Calculate volume ratio
avg_vol = sum(volumes[-20:]) / 20 if len(volumes) >= 20 else sum(volumes) / len(volumes)
current_vol = volumes[-1]
vol_ratio = current_vol / avg_vol if avg_vol > 0 else 0
# Signal conditions
reasons = []
# Condition 1: Uptrend pullback
if current_price > ema21 and rsi < 38 and vol_ratio > 1.8:
reasons.append("UPTREND PULLBACK")
# Condition 2: Deep momentum dip
if current_price > ema9 and rsi < 30 and vol_ratio > 2.0:
reasons.append("DEEP MOMENTUM DIP")
# Condition 3: Volume breakout
if vol_ratio > 3.0 and rsi < 60 and current_price > ema21:
reasons.append("VOLUME BREAKOUT")
if reasons:
signals_found += 1
log_signal(symbol, current_price, rsi, vol_ratio, " + ".join(reasons))
# Small delay to respect rate limits
time.sleep(0.2)
except Exception as e:
print(f"[ERROR] Processing {symbol}: {e}")
continue
print(f"\n[INFO] Scan complete. Signals found: {signals_found}")
# ─────────────────────────────────────────────────────────────
# MAIN LOOP
# ─────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("="*50)
print("[INIT] Market Scanner v2.0 | Paper Mode Only")
print("[INFO] Signals are for EDUCATIONAL purposes.")
print("[INFO] Trade at your own risk.")
print("="*50)
try:
while True:
try:
scan_market()
except Exception as e:
print(f"\n[CRASH] Unexpected error: {e}")
print(f"\n[SLEEP] Next scan in {SCAN_INTERVAL_SEC} seconds...")
print("Press Ctrl+C to stop\n")
time.sleep(SCAN_INTERVAL_SEC)
except KeyboardInterrupt:
print("\n\n[SHUTDOWN] Scanner stopped by user.")
print("[INFO] Thank you for using Market Scanner!")
The script logs signals to signals.log and prints them to console. No API keys. No paid libraries. Pure Python.
7. Step 5: Risk Filters & Paper Trading Mode
A signal without risk management is just gambling. Before connecting this to any exchange, implement:
- Position Sizing: Never risk >1% of capital per signal.
- ATR-Based Stops: Calculate 14-period ATR. Place stop loss at
entry - 1.5 * ATR. - Take Profit Ladder: 50% at 1:1.5 RR, 50% at 1:3 RR. Trail the rest.
- Max Concurrency: Limit to 3 open positions. Crypto correlates heavily. Diversification is an illusion in a BTC-dominated market.
- Paper Trade First: Run this script for 60–90 days. Log every signal. Track win rate, max drawdown, and expectancy. If it doesn’t work on paper, it won’t work live.
Add this function to the scanner to simulate trades:
def paper_trade(symbol, entry, stop, target, risk_pct=0.01):
rr = (target - entry) / (entry - stop)
print(f"[PAPER] {symbol} | Entry: {entry} | SL: {stop} | TP: {target} | R:R: {rr:.2f}")
# Log to CSV, track in Excel/Notion, review weekly
8. Step 6: Running It 24/7
You can’t leave a laptop running. Deploy it properly:
- VPS: Oracle Cloud Free Tier (4 ARM cores, 24GB RAM) or $5 DigitalOcean droplet.
- Systemd Service
[Unit]
Description=Crypto Market Scanner
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/scanner
ExecStart=/usr/bin/python3 /home/ubuntu/scanner/market_scanner.py
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
- Enable with:
sudo systemctl enable scanner.service && sudo systemctl start scanner - Alerting: Add a Telegram webhook to
log_signal()to push alerts to your phone. Free, instant, zero spam. - Monitoring: Use
journalctl -u scanner -for set up Logrotate to prevent disk fill.
9. The Unspoken Rules (What Tutorials Won’t Tell You)
- Free APIs aren’t free forever. CoinGecko limits scale with traffic. If you hammer endpoints, you get 429s. Space requests. Cache aggressively.
- Edge decays. A signal that works in a bull market fails in a range. Re-optimize filters quarterly. Track regime shifts.
- Liquidity is king. Illiquid pairs look beautiful on charts. They destroy you on execution. Always filter by 24h volume > $30M.
- Slippage & fees matter. Backtest with 0.1% taker fee + 0.05% slippage. If your strategy doesn’t survive that, it’s theoretical.
- Never automate execution without manual oversight. Bots don’t understand news, exchange outages, or black swans. You’re the circuit breaker.
Build, Iterate, Survive
This scanner won’t make you rich. It will make you disciplined. It forces you to define your edge, test it objectively, and execute without emotion. That’s the real advantage.
Start with paper trading. Log everything. Review weekly. Tweak filters. Add one variable at a time. When your 60-day expectancy is consistently positive, consider scaling to a testnet or micro-live account.
The market doesn’t reward complexity. It rewards consistency. Build the system. Trust the process. Survive long enough to let compounding work.
If you found this useful, clap, share, and follow for more quantitative breakdowns, Python trading systems, and no-hype market analysis. The code is yours. The edge is yours to refine.
If you enjoyed this, please:
- 👏 Clap (up to 50 times!)
- 💬 Leave a comment
- 🔗 Share with fellow traders
- ⭐ Star the GitHub repo
- **all for trading — **Stop Losing Money
Thanks for reading!
Questions? Find me on:
Also, **Telegram** for free trading signals. No privet or pay groups.
This article is for educational purposes only. It does not constitute financial advice. Cryptocurrency trading carries significant risk. Always paper trade before deploying capital. Past performance does not guarantee future results.
메타데이터
- post_id
- 2f3323b66bfd
- slug
- how-i-built-a-free-crypto-market-scanner-that-spots-high-probability-trades-no-paid-apis-no-hype-2f3323b66bfd
- url
- https://medium.com/coinmonks/how-i-built-a-free-crypto-market-scanner-that-spots-high-probability-trades-no-paid-apis-no-hype-2f3323b66bfd
- canonical_url
- https://medium.com/coinmonks/how-i-built-a-free-crypto-market-scanner-that-spots-high-probability-trades-no-paid-apis-no-hype-2f3323b66bfd
- author_url
- https://medium.com/@skyair
- status
- ok
- fetched_at
- 2026-06-23 06:34:20