Binance Futures lists 473 perpetual contracts as of this writing. APIndicators tracks every one of them, ranked by rolling volume. But here is the uncomfortable truth: if you are running an algo trading strategy, you probably should not touch 400 of them. They are too illiquid, too correlated with bigger pairs, or dead to the point where fills are unreliable.
This post walks through how to filter the 473-pair universe down to a sensible 15-30 pair whitelist, why that makes your backtests more honest, and how to use APIndicators' rank field to automate the filtering.
Why Pair Count Matters
Three specific reasons to prune aggressively:
- Slippage kills edge on low-volume pairs. A 0.15% bid-ask spread eats a week's edge on many retail strategies.
- Correlated bets compound risk. SOLUSDT, BNBUSDT, AVAXUSDT often move together. Running one strategy on 20 correlated alts is closer to 3-4 effective bets.
- Testing surface area explodes. 473 pairs times 8 timeframes times 5 strategies = 18,920 strategy-pair-timeframe combinations. Most are pure noise.
Pruning the universe is the single highest-leverage decision in most retail quant workflows.
The Three Filters That Matter
Filter 1: Daily USD Volume
Rank pairs by rolling 24h quote volume. Keep the top N.
APIndicators exposes a rank field on every pair, updated daily based on 24h USD volume. Rank 1 is BTCUSDT, 2 is ETHUSDT, and so on. A simple top-20 filter:
import requests
import os
API_KEY = os.environ["APINDICATORS_API_KEY"]
def get_top_pairs(n=20):
url = "https://api.apindicators.com/v1/pairs"
headers = {"Authorization": f"Bearer {API_KEY}"}
r = requests.get(url, headers=headers, params={"limit": n, "sort": "rank"})
pairs = r.json()["data"]
return [p["symbol"] for p in pairs]
top_20 = get_top_pairs(20)
print(top_20)
Expected output (varies daily): ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', ...]
For most strategies, top 15-25 is the right range. Top 5 is too narrow (misses alt opportunities), top 50+ dilutes into illiquid names.
Filter 2: Spread and Depth
Daily volume is necessary but not sufficient. A pair can have high volume but wide spreads during the hours you want to trade. A spread filter:
def get_spread(symbol):
url = f"https://api.apindicators.com/v1/pairs/{symbol}/orderbook-summary"
headers = {"Authorization": f"Bearer {API_KEY}"}
r = requests.get(url, headers=headers)
data = r.json()
spread_bps = (data["ask"] - data["bid"]) / data["bid"] * 10000
return spread_bps
acceptable_pairs = [
symbol for symbol in top_20
if get_spread(symbol) < 5.0
]
5 bps is a reasonable ceiling for liquid Binance Futures pairs. Anything wider means you are paying slippage that will eat a meaningful chunk of your edge.
Filter 3: Activity / Liveness
Some pairs have "volume" that is actually wash-trading or infrequent large prints. A better liveness signal: number of distinct 1-minute candles with non-zero volume in the last 24 hours.
SELECT
p.symbol,
p.rank,
COUNT(CASE WHEN c.volume > 0 THEN 1 END) AS active_minutes_24h
FROM pairs p
JOIN candles c ON c.pair_id = p.id
WHERE c.interval = '1m'
AND c.opened_at >= NOW() - INTERVAL '24 hours'
GROUP BY p.symbol, p.rank
HAVING COUNT(CASE WHEN c.volume > 0 THEN 1 END) >= 1200
ORDER BY p.rank
LIMIT 30;
1200 out of 1440 possible minutes = 83% active. That threshold eliminates pairs that sit idle most of the session.
The Production Whitelist Pattern
Combine all three filters into a single daily-refreshed whitelist:
def build_whitelist():
top_pairs = get_top_pairs(50)
whitelist = []
for symbol in top_pairs:
spread_bps = get_spread(symbol)
if spread_bps > 5.0:
continue
activity = get_activity_score(symbol)
if activity < 0.80:
continue
whitelist.append(symbol)
if len(whitelist) >= 25:
break
return whitelist
daily_whitelist = build_whitelist()
Refresh this daily, persist it somewhere, and reject any trade attempt on a symbol not in the whitelist.
What About Newly-Listed Hot Pairs?
When a new Binance Futures listing trends (memecoins, new L1s), it can temporarily hit top 20 volume and look attractive. Be careful. New listings often have:
- Wild volatility that breaks trained ML features
- Thin orderbooks outside peak hours
- Fast rank movement (top 20 today, rank 80 next week)
APIndicators includes a listed_at field. Exclude pairs listed fewer than 30 days ago from your whitelist unless you are explicitly trading the listing-momentum regime.
def is_mature(pair):
listed_days = (now() - parse_date(pair["listed_at"])).days
return listed_days >= 30
Diversification Within the Whitelist
Once you have 20-25 pairs, you still need to watch correlation. A simple approach:
- Compute 30-day return correlations between all pairs in the whitelist
- Cluster pairs with correlation > 0.8 into groups
- Allow at most 2-3 simultaneous positions per cluster
This prevents the "I had 8 alt longs and Bitcoin dropped 4%" blowup.
Practical Takeaways
- Do not trade all 473 pairs. Top 15-25 is usually correct.
- Refresh your whitelist daily using volume rank plus spread plus liveness filters.
- Exclude new listings unless specifically trading listing momentum.
- Cap simultaneous positions by correlation cluster, not just pair count.
- Persist the whitelist and alert yourself when pairs enter or leave it.
APIndicators exposes rank, listed_at, and full orderbook depth data for all 473 pairs. Full docs at apindicators.com/docs or sign up at apindicators.com/pricing to start filtering the right universe today.