TickAtlas
Developer 9 min read · March 28, 2026

Rate Limiting Strategies: How to Maximize Your API Quota

Practical techniques for working within API rate limits. Learn caching, request batching, smart polling, and quota management to get the most out of every API call.

CG
By the TickAtlas team

Why Rate Limits Exist

Rate limits protect both you and the API. Without them, a single runaway loop in your code could burn through your entire monthly quota in minutes. The TickAtlas API enforces per-minute and per-day limits based on your plan tier. Here is how to work within those limits efficiently.

Strategy 1: Client-Side Caching

The single most effective optimization. Cache API responses locally and only fetch new data when the cache expires. Real-time data has a 2-second freshness window; indicator data on H1 only changes every hour.

python
import requests
import time
from functools import lru_cache

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

class CachedClient:
    def __init__(self):
        self._cache = {}
        self._cache_ttl = {}

    def get_indicators(self, symbol: str, timeframe: str,
                       indicators: str, ttl: int = 60) -> dict:
        """Fetch indicators with local caching."""
        cache_key = f"{symbol}:{timeframe}:{indicators}"
        now = time.time()

        # Return cached if still fresh
        if cache_key in self._cache:
            if now - self._cache_ttl[cache_key] < ttl:
                return self._cache[cache_key]

        # Fetch fresh data
        resp = requests.get(f"{BASE_URL}/indicators", params={
            "symbol": symbol,
            "timeframe": timeframe,
            "indicators": indicators
        }, headers=HEADERS)
        resp.raise_for_status()
        data = resp.json()

        self._cache[cache_key] = data
        self._cache_ttl[cache_key] = now
        return data

# Usage: H1 indicators cached for 55 minutes
client = CachedClient()
data = client.get_indicators("EURUSD", "H1", "RSI_14,MACD", ttl=3300)

Strategy 2: Request Batching

Fetch multiple indicators in a single API call instead of making separate requests for each one. One call with 5 indicators costs the same as one call with 1 indicator.

python
# BAD: 5 separate API calls (5 requests)
rsi = client.get("/indicators?symbol=EURUSD&indicators=RSI_14")
macd = client.get("/indicators?symbol=EURUSD&indicators=MACD")
bb = client.get("/indicators?symbol=EURUSD&indicators=BBANDS_20")
adx = client.get("/indicators?symbol=EURUSD&indicators=ADX_14")
atr = client.get("/indicators?symbol=EURUSD&indicators=ATR_14")

# GOOD: 1 API call with all indicators (1 request)
all_data = client.get(
    "/indicators?symbol=EURUSD&indicators=RSI_14,MACD,BBANDS_20,ADX_14,ATR_14"
)
# 80% reduction in API usage

Strategy 3: Smart Polling Intervals

Match your polling frequency to the timeframe you are analyzing. There is no reason to check H1 indicators every 30 seconds — the data only updates once per hour.

Timeframe Recommended Interval Requests/Hour
M560 seconds60
M153 minutes20
H15 minutes12
H415 minutes4
D11 hour1

Strategy 4: Exponential Backoff

When you hit a rate limit (HTTP 429), do not immediately retry. Wait, then try again with increasing delays.

python
import time

def request_with_backoff(url: str, params: dict,
                         max_retries: int = 5) -> dict:
    """Make API request with exponential backoff on rate limits."""
    for attempt in range(max_retries):
        resp = requests.get(url, params=params, headers=HEADERS)

        if resp.status_code == 200:
            return resp.json()

        if resp.status_code == 429:
            wait = 2 ** attempt  # 1, 2, 4, 8, 16 seconds
            retry_after = resp.headers.get("Retry-After", wait)
            print(f"Rate limited. Waiting {retry_after}s (attempt {attempt + 1})")
            time.sleep(float(retry_after))
        else:
            resp.raise_for_status()

    raise Exception(f"Max retries ({max_retries}) exceeded")

Strategy 5: Usage Monitoring

python
class UsageTracker:
    """Track API usage to stay within limits."""
    def __init__(self, daily_limit: int):
        self.daily_limit = daily_limit
        self.calls_today = 0
        self.reset_date = None

    def can_make_request(self) -> bool:
        today = datetime.utcnow().date()
        if self.reset_date != today:
            self.calls_today = 0
            self.reset_date = today

        return self.calls_today < self.daily_limit

    def record_request(self):
        self.calls_today += 1

    def remaining(self) -> int:
        return max(0, self.daily_limit - self.calls_today)

    def usage_percent(self) -> float:
        return (self.calls_today / self.daily_limit) * 100

# Usage
tracker = UsageTracker(daily_limit=50000)  # Starter plan

if tracker.can_make_request():
    data = client.get_indicators("EURUSD", "H1", "RSI_14,MACD")
    tracker.record_request()
    print(f"Usage: {tracker.usage_percent():.1f}% ({tracker.remaining()} remaining)")
else:
    print("Daily limit reached. Serving from cache only.")

Strategy 6: Priority-Based Fetching

When running low on quota, prioritize the most important data. Fetch only the symbols with active signals rather than scanning everything.

python
def priority_scan(tracker: UsageTracker,
                  watchlist: list[str]) -> list[dict]:
    """Scan with priority when quota is low."""
    usage_pct = tracker.usage_percent()

    if usage_pct < 50:
        # Plenty of quota — scan everything
        symbols_to_check = watchlist
    elif usage_pct < 80:
        # Getting tight — only check top 5 pairs
        symbols_to_check = watchlist[:5]
    else:
        # Almost out — only check pairs with recent signals
        symbols_to_check = get_pairs_with_recent_signals()

    results = []
    for symbol in symbols_to_check:
        if tracker.can_make_request():
            data = client.get_indicators(symbol, "H1", "RSI_14")
            tracker.record_request()
            results.append(data)

    return results

Quick Wins Summary

Batch indicators in one call

Saves 60-80% of requests immediately. The single biggest optimization.

Cache H1+ data for 5 minutes

H1 candles close once per hour. Fetching more often than every 5 minutes is wasteful.

Handle 429 errors gracefully

Exponential backoff prevents cascade failures. Read the Retry-After header.

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.