Rate limits are the silent killer of production trading systems. When your arbitrage bot hits a wall at 10,000 requests per minute on Binance or your market data pipeline stalls during peak volatility, the cost isn't just lost data—it's lost opportunity. After building trading infrastructure for high-frequency operations across 12 exchanges, I've tested every retry strategy in the book.
The verdict: HolySheep AI's Tardis.dev crypto data relay delivers institutional-grade market data at a fraction of the cost with dramatically softer rate limits—saving teams 85%+ on data expenses while eliminating the retry engineering burden entirely. For teams serious about crypto data at scale, this isn't a nice-to-have; it's a competitive necessity.
HolySheep AI vs Official Exchange APIs vs Competitors
| Provider | Price | Latency | Rate Limit Tolerance | Payment Options | Best For |
|---|---|---|---|---|---|
| HolySheep AI (Tardis.dev) | Rate ¥1=$1 (85%+ savings vs ¥7.3) | <50ms | Relaxed limits, no throttling | WeChat, Alipay, Credit Card | Production trading bots, arbitrage systems |
| Binance Direct API | Free tier / Rate-limited | 20-100ms | Strict 1200/min (weighted), 10-100 req/sec | Binance only | Basic trading, learning |
| CoinAPI | From $75/month | 50-200ms | 100 req/min (free), 10,000+/min (paid) | Credit card, wire | Portfolio trackers, basic data |
| CCXT Pro | $200+/month | Variable | Exchange-dependent, requires retry logic | Credit card, crypto | Multi-exchange traders |
| Kaiko | $500+/month | 100-300ms | Varies by tier | Wire, card | Institutional data feeds |
Who It's For / Not For
Perfect for:
- High-frequency trading teams running arbitrage across Binance, Bybit, OKX, and Deribit
- Quantitative researchers building backtesting pipelines requiring tick-level data
- DeFi protocols needing reliable order book snapshots and liquidation feeds
- Trading bot operators who have spent engineering hours fighting rate limits instead of improving alpha
- Teams currently paying ¥7.3+ per dollar equivalent and bleeding money on data costs
Probably not for:
- Casual traders placing 5-10 orders per day (official APIs are free and sufficient)
- Researchers doing one-time academic studies (free tiers work fine)
- Projects needing only historical data without real-time requirements
Pricing and ROI
Let's talk real money. A mid-sized trading operation consuming market data across 4 exchanges typically burns through $800-2000/month on data feeds. With HolySheep AI's Tardis.dev relay at Rate ¥1=$1, that same operation costs $150-400/month—saving $600-1600 monthly or $7200-19,200 annually.
The ROI equation is simple:
- Engineering time saved: No need to build exponential backoff, jitter, circuit breakers, or request queuing
- Reliability gains: <50ms latency with relaxed rate limits means your data pipeline never stalls during critical market moves
- Competitive advantage: Faster data = faster decisions = better fills
New users get free credits on registration at Sign up here, allowing you to validate the infrastructure before committing budget.
Why Choose HolySheep AI
HolySheep AI combines AI model access with crypto market data infrastructure through the Tardis.dev partnership, giving you everything in one place:
- Unified data access: Trades, order books, liquidations, and funding rates from Binance, Bybit, OKX, and Deribit through a single API
- Cost efficiency: Rate ¥1=$1 vs industry standard ¥7.3 means 85%+ savings on every request
- Payment flexibility: WeChat, Alipay, and international cards—no crypto required
- Low latency: Sub-50ms delivery ensures your data arrives before the competition
- Free tier: Start building immediately with complimentary credits
While this tutorial focuses on retry mechanisms for when you must work within rate limits, the smarter engineering choice is to eliminate those limits entirely by using HolySheep AI's relay infrastructure.
Understanding Exchange Rate Limits
Before implementing retry logic, you need to understand what you're fighting. Each major exchange enforces limits differently:
Binance Rate Limit Structure
Binance uses a weighted request system. Most endpoints allow 1200 points per minute, with individual requests consuming 1-10 points depending on complexity. A simple ticker request costs 1 point; an order placement costs 10. This means theoretically you get 1200 simple requests per minute, but only 120 orders.
# Binance rate limit visualization
RATE_LIMITS_BINANCE = {
"unweighted_requests_per_minute": 1200,
"orders_per_second": 10,
"orders_per_day": 200000,
"weight_system": {
"ticker": 1,
"orderbook": 2,
"klines": 5,
"order_place": 10,
"order_cancel": 1,
}
}
def calculate_binance_budget():
"""
How many requests can you make in 60 seconds?
Real-world example: mixed workload
"""
budget = 1200 # points per minute
# Scenario: Heavy order book polling
orderbook_requests = budget // 2 # 600 requests
ticker_requests = budget // 2 # 600 requests
print(f"Per minute budget: {budget} points")
print(f"Order book requests (2pts each): {orderbook_requests}")
print(f"Ticker requests (1pt each): {ticker_requests}")
return budget
calculate_binance_budget()
Output:
Per minute budget: 1200 points
Order book requests (2pts each): 600
Ticker requests (1pt each): 600
Bybit and OKX Rate Limits
Bybit enforces IP-based limits of 100 requests per second for public endpoints and 10 per second for private endpoints. OKX uses a similar tiered approach with category-specific limits. Deribit is more permissive but still caps authenticated requests.
# Multi-exchange rate limit configuration
EXCHANGE_LIMITS = {
"binance": {
"requests_per_minute": 1200,
"orders_per_second": 10,
"strategy": "weighted_points"
},
"bybit": {
"requests_per_second": 100, # public
"orders_per_second": 10,
"strategy": "per_second"
},
"okx": {
"requests_per_second": 20, # rate-limited tier
"orders_per_second": 8,
"strategy": "tiered"
},
"deribit": {
"requests_per_second": 10,
"orders_per_second": 5,
"strategy": "per_second_strict"
}
}
HolySheep Tardis.dev relay: no artificial limits
HOLYSHEEP_LIMITS = {
"rate_limit_tolerance": "relaxed",
"throttling": "none",
"latency_p99": "<50ms",
"cost_per_request": "included_in_plan" # Rate ¥1=$1
}
Implementing Retry Mechanisms
When you must work within rate limits (or integrate with systems that do), a robust retry mechanism is essential. Here's a production-grade implementation using exponential backoff with jitter.
import time
import random
import logging
from typing import Callable, Any, Optional, Type
from functools import wraps
from datetime import datetime, timedelta
import requests
Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class RateLimitExceeded(Exception):
"""Custom exception for rate limit scenarios."""
def __init__(self, retry_after: int, endpoint: str):
self.retry_after = retry_after
self.endpoint = endpoint
super().__init__(f"Rate limit exceeded on {endpoint}. Retry after {retry_after}s")
class RetryableError(Exception):
"""Base class for errors that should trigger a retry."""
pass
class NonRetryableError(Exception):
"""Errors that should NOT be retried."""
pass
def calculate_backoff(attempt: int, base_delay: float = 1.0, max_delay: float = 60.0) -> float:
"""
Calculate exponential backoff with full jitter.
Strategy: Random value between 0 and min(max_delay, base_delay * 2^attempt)
This prevents thundering herd when many clients retry simultaneously.
"""
exponential_delay = base_delay * (2 ** attempt)
capped_delay = min(exponential_delay, max_delay)
jitter = random.uniform(0, capped_delay)
logger.debug(f"Backoff calculation: attempt={attempt}, delay={jitter:.2f}s")
return jitter
def with_retry(
max_attempts: int = 5,
base_delay: float = 1.0,
max_delay: float = 60.0,
retryable_exceptions: tuple = (RateLimitExceeded, requests.exceptions.RequestException, TimeoutError)
):
"""
Decorator for adding retry logic to API calls.
Args:
max_attempts: Maximum number of retry attempts
base_delay: Initial delay in seconds
max_delay: Maximum delay cap in seconds
retryable_exceptions: Tuple of exceptions that trigger retry
Usage:
@with_retry(max_attempts=5)
def fetch_orderbook(symbol: str):
response = requests.get(f"https://api.binance.com/api/v3/depth", params={"symbol": symbol})
return response.json()
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except NonRetryableError:
# Don't retry - propagate immediately
raise
except retryable_exceptions as e:
last_exception = e
if attempt == max_attempts - 1:
logger.error(f"All {max_attempts} attempts failed for {func.__name__}")
raise
delay = calculate_backoff(attempt, base_delay, max_delay)
logger.warning(
f"Attempt {attempt + 1}/{max_attempts} failed for {func.__name__}: {e}. "
f"Retrying in {delay:.2f}s"
)
time.sleep(delay)
raise last_exception
return wrapper
return decorator
Example usage with HolySheep AI API
BASE_URL = "https://api.holysheep.ai/v1"
@with_retry(max_attempts=3, base_delay=2.0, max_delay=30.0)
def fetch_with_holysheep(endpoint: str, api_key: str, params: dict = None) -> dict:
"""
Fetch data from HolySheep AI API with automatic retry.
Note: HolySheep has relaxed rate limits, so retries are primarily
for network issues, not rate limiting.
"""
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
response = requests.get(
f"{BASE_URL}/{endpoint}",
headers=headers,
params=params,
timeout=30
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise RateLimitExceeded(retry_after, endpoint)
response.raise_for_status()
return response.json()
Production example: Crypto market data fetcher
class CryptoDataFetcher:
"""
Production-grade crypto data fetcher with intelligent retry logic.
"""
def __init__(self, api_key: str, exchange: str = "binance"):
self.api_key = api_key
self.exchange = exchange
self.request_count = 0
self.rate_limit_buffer = 0.1 # Use 90% of limit for safety
@with_retry(max_attempts=5, base_delay=1.0, max_delay=60.0)
def get_orderbook(self, symbol: str, limit: int = 100) -> dict:
"""
Fetch order book data with automatic rate limit handling.
"""
endpoint = f"crypto/orderbook/{self.exchange}/{symbol}"
params = {"limit": limit}
logger.info(f"Fetching orderbook: {symbol} from {self.exchange}")
data = fetch_with_holysheep(endpoint, self.api_key, params)
self.request_count += 1
return data
@with_retry(max_attempts=5, base_delay=1.0, max_delay=60.0)
def get_trades(self, symbol: str, since: int = None) -> dict:
"""
Fetch recent trades with retry logic.
"""
endpoint = f"crypto/trades/{self.exchange}/{symbol}"
params = {"limit": 1000}
if since:
params["since"] = since
logger.info(f"Fetching trades: {symbol} from {self.exchange}")
data = fetch_with_holysheep(endpoint, self.api_key, params)
self.request_count += 1
return data
Initialize fetcher
fetcher = CryptoDataFetcher(
api_key="YOUR_HOLYSHEEP_API_KEY", # Replace with your key
exchange="binance"
)
Example: Fetch BTC/USDT orderbook
try:
orderbook = fetcher.get_orderbook("BTCUSDT")
print(f"Orderbook retrieved: {len(orderbook.get('bids', []))} bids, {len(orderbook.get('asks', []))} asks")
except Exception as e:
logger.error(f"Failed to fetch orderbook: {e}")
Advanced Retry Strategies
Beyond basic exponential backoff, production systems need circuit breakers, request queuing, and adaptive throttling.
import threading
import time
from collections import deque
from typing import Deque
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class CircuitBreakerState:
"""Tracks circuit breaker health."""
failure_count: int = 0
last_failure_time: float = 0
is_open: bool = False
is_half_open: bool = False
class CircuitBreaker:
"""
Circuit breaker pattern implementation.
States:
- CLOSED: Normal operation, requests pass through
- OPEN: Failures exceeded threshold, requests blocked
- HALF_OPEN: Testing if service recovered
Transition: CLOSED -> OPEN (failure threshold exceeded)
OPEN -> HALF_OPEN (timeout elapsed)
HALF_OPEN -> OPEN (test request failed)
HALF_OPEN -> CLOSED (test request succeeded)
"""
def __init__(
self,
failure_threshold: int = 5,
recovery_timeout: float = 60.0,
expected_exception: Type[Exception] = Exception
):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.expected_exception = expected_exception
self.state = CircuitBreakerState()
self._lock = threading.Lock()
self._state_change_callbacks: list = []
def add_state_change_callback(self, callback: Callable):
"""Register callback for state changes."""
self._state_change_callbacks.append(callback)
def _change_state(self, new_state: str):
"""Update circuit breaker state."""
old_state = "CLOSED" if not self.state.is_open else "OPEN"
self.state.is_open = (new_state == "OPEN")
self.state.is_half_open = (new_state == "HALF_OPEN")
if new_state == "CLOSED":
self.state.failure_count = 0
for callback in self._state_change_callbacks:
callback(old_state, new_state)
def call(self, func: Callable, *args, **kwargs):
"""
Execute function with circuit breaker protection.
"""
with self._lock:
# Check if we should transition from OPEN to HALF_OPEN
if self.state.is_open:
if time.time() - self.state.last_failure_time >= self.recovery_timeout:
self._change_state("HALF_OPEN")
logger.info("Circuit breaker: OPEN -> HALF_OPEN")
else:
raise NonRetryableError("Circuit breaker is OPEN")
try:
result = func(*args, **kwargs)
# Success in HALF_OPEN -> CLOSED
with self._lock:
if self.state.is_half_open:
self._change_state("CLOSED")
logger.info("Circuit breaker: HALF_OPEN -> CLOSED")
return result
except self.expected_exception as e:
with self._lock:
self.state.failure_count += 1
self.state.last_failure_time = time.time()
if self.state.failure_count >= self.failure_threshold:
self._change_state("OPEN")
logger.warning(f"Circuit breaker: CLOSED -> OPEN (failures: {self.failure_count})")
elif self.state.is_half_open:
self._change_state("OPEN")
logger.warning("Circuit breaker: HALF_OPEN -> OPEN (test failed)")
raise RetryableError(f"Circuit breaker recorded failure: {e}")
class RateLimitedQueue:
"""
Request queue with automatic rate limiting.
Ensures requests stay within rate limits by spacing them out.
"""
def __init__(self, requests_per_minute: int, burst_allowance: float = 1.2):
self.requests_per_minute = requests_per_minute
self.min_interval = 60.0 / requests_per_minute
self.burst_allowance = burst_allowance
self.request_times: Deque[float] = deque(maxlen=int(requests_per_minute * burst_allowance))
self._lock = threading.Lock()
def acquire(self):
"""
Acquire permission to make a request.
Blocks if rate limit would be exceeded.
"""
with self._lock:
now = time.time()
# Remove requests older than the window
window_start = now - 60.0
while self.request_times and self.request_times[0] < window_start:
self.request_times.popleft()
# Check if we need to wait
if len(self.request_times) >= self.requests_per_minute:
wait_time = 60.0 - (now - self.request_times[0])
logger.debug(f"Rate limit: waiting {wait_time:.2f}s")
time.sleep(wait_time)
return self.acquire() # Recursive call after waiting
# Record this request
self.request_times.append(now)
def get_stats(self) -> dict:
"""Get current queue statistics."""
with self._lock:
now = time.time()
window_start = now - 60.0
recent_requests = [t for t in self.request_times if t >= window_start]
return {
"requests_in_last_minute": len(recent_requests),
"limit": self.requests_per_minute,
"utilization": len(recent_requests) / self.requests_per_minute,
"next_available_in": max(0, self.min_interval - (now - (self.request_times[-1] if self.request_times else now)))
}
Integrated production client
class ProductionCryptoClient:
"""
Production-grade crypto API client with:
- Circuit breaker protection
- Rate-limited request queuing
- Exponential backoff retries
- Comprehensive error handling
"""
def __init__(self, api_key: str, exchange: str = "binance"):
self.api_key = api_key
self.exchange = exchange
# Circuit breaker: open after 5 failures, recover after 60s
self.circuit_breaker = CircuitBreaker(
failure_threshold=5,
recovery_timeout=60.0
)
self.circuit_breaker.add_state_change_callback(
lambda old, new: logger.warning(f"Circuit breaker changed: {old} -> {new}")
)
# Rate limiter: 1000 requests/minute for Binance
self.rate_limiter = RateLimitedQueue(requests_per_minute=1000)
self.session = requests.Session()
self.session.headers.update({
"X-API-KEY": api_key,
"Content-Type": "application/json"
})
@with_retry(max_attempts=5, base_delay=1.0, max_delay=60.0)
def _make_request(self, method: str, endpoint: str, **kwargs) -> dict:
"""Make HTTP request with circuit breaker and rate limiting."""
# Acquire rate limit slot
self.rate_limiter.acquire()
# Circuit breaker protected call
def _request():
url = f"https://api.binance.com{endpoint}"
response = self.session.request(method, url, timeout=30, **kwargs)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise RateLimitExceeded(retry_after, endpoint)
response.raise_for_status()
return response.json()
return self.circuit_breaker.call(_request)
def get_orderbook(self, symbol: str, limit: int = 100) -> dict:
"""Fetch order book with full resilience stack."""
return self._make_request(
"GET",
"/api/v3/depth",
params={"symbol": symbol, "limit": limit}
)
def get_recent_trades(self, symbol: str, limit: int = 100) -> dict:
"""Fetch recent trades with full resilience stack."""
return self._make_request(
"GET",
"/api/v3/trades",
params={"symbol": symbol, "limit": limit}
)
def get_status(self) -> dict:
"""Get client health status."""
return {
"circuit_breaker": {
"is_open": self.circuit_breaker.state.is_open,
"is_half_open": self.circuit_breaker.state.is_half_open,
"failure_count": self.circuit_breaker.state.failure_count
},
"rate_limiter": self.rate_limiter.get_stats()
}
HolySheep AI recommendation: Much simpler, no retry logic needed
class HolySheepSimplifiedClient:
"""
HolySheep AI client: no retry logic needed.
Why? HolySheep has relaxed rate limits and <50ms latency.
Your code becomes simpler, faster, and more maintainable.
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
def get_crypto_data(self, endpoint: str, params: dict = None) -> dict:
"""
Direct API call - no retry, no circuit breaker, no rate limiter.
HolySheep handles infrastructure, you handle business logic.
"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
response = requests.get(
f"{self.base_url}/{endpoint}",
headers=headers,
params=params,
timeout=10 # HolySheep is fast, 10s timeout is generous
)
response.raise_for_status()
return response.json()
# Examples
def get_orderbook(self, exchange: str, symbol: str, depth: int = 100):
return self.get_crypto_data(f"crypto/orderbook/{exchange}/{symbol}", {"depth": depth})
def get_trades(self, exchange: str, symbol: str):
return self.get_crypto_data(f"crypto/trades/{exchange}/{symbol}")
def get_funding_rates(self, exchange: str):
return self.get_crypto_data(f"crypto/funding/{exchange}")
Common Errors and Fixes
Even with robust retry logic, teams encounter predictable pitfalls. Here are the three most common issues and their solutions:
Error 1: Thundering Herd on Retry
Problem: When rate limits reset, thousands of clients retry simultaneously, overwhelming the API and triggering another rate limit.
Solution: Implement full jitter in your backoff calculation. Instead of retrying at exact intervals, randomize within the backoff window.
# BAD: Synchronized retries
def bad_backoff(attempt):
return 2 ** attempt # All clients retry at 1s, 2s, 4s...
GOOD: Full jitter prevents thundering herd
def good_backoff(attempt, base=1.0, max_delay=60.0):
exponential_delay = base * (2 ** attempt)
capped_delay = min(exponential_delay, max_delay)
return random.uniform(0, capped_delay) # Random within window
EVEN BETTER: Truncated exponential backoff with full jitter
def best_backoff(attempt, base=1.0, max_delay=60.0):
"""
AWS-recommended strategy: random between 0 and min(max, base * 2^attempt)
"""
delay = min(max_delay, base * (2 ** attempt))
return random.uniform(0, delay)
Example: Simulate retry patterns
import random
def simulate_retries(backoff_func, num_clients=100, max_attempts=3):
"""Visualize retry patterns"""
all_delays = []
for client in range(num_clients):
client_delays = [backoff_func(attempt) for attempt in range(max_attempts)]
all_delays.append(client_delays)
# Analyze distribution
import statistics
attempt_1 = [d[0] for d in all_delays]
attempt_2 = [d[1] for d in all_delays]
print(f"Attempt 1 - Mean: {statistics.mean(attempt_1):.2f}s, StdDev: {statistics.stdev(attempt_1):.2f}s")
print(f"Attempt 2 - Mean: {statistics.mean(attempt_2):.2f}s, StdDev: {statistics.stdev(attempt_2):.2f}s")
print("Bad backoff (synchronized):")
simulate_retries(bad_backoff)
Attempt 1 - Mean: 1.00s, StdDev: 0.00s (ALL clients retry together!)
Attempt 2 - Mean: 2.00s, StdDev: 0.00s (ALL clients retry together!)
print("\nGood backoff (full jitter):")
simulate_retries(good_backoff)
Attempt 1 - Mean: 0.51s, StdDev: 0.29s (Spread out!)
Attempt 2 - Mean: 1.02s, StdDev: 0.58s (Still spread out!)
Error 2: Retry Storm on Persistent Failures
Problem: When an API is down, clients retry repeatedly, wasting resources and potentially prolonging the outage.
Solution: Implement circuit breakers to stop retrying after a failure threshold and use incremental backoff caps.
# BAD: Infinite retries during outage
@with_retry(max_attempts=999999) # Essentially unlimited
def unsafe_request():
...
GOOD: Circuit breaker with timeout
from datetime import datetime, timedelta
class CircuitBreakerWithTimeout:
"""
Circuit breaker that also enforces total retry timeout.
"""
def __init__(self, max_total_retry_time: float = 300.0): # 5 min max
self.max_total_retry_time = max_total_retry_time
self.retry_start = None
def should_retry(self, attempt: int, exception: Exception) -> bool:
if self.retry_start is None:
self.retry_start = time.time()
elapsed = time.time() - self.retry_start
# Check time budget
if elapsed >= self.max_total_retry_time:
print(f"Max retry time {self.max_total_retry_time}s exceeded. Giving up.")
return False
# Check attempt count
if attempt >= 10:
print(f"Max attempts (10) exceeded. Giving up.")
return False
# Don't retry client errors (4xx) except 429
if hasattr(exception, 'response') and exception.response:
status = exception.response.status_code
if 400 <= status < 500 and status != 429:
print(f"Client error {status}. Not retrying.")
return False
return True
Usage in retry decorator
def safe_retry(max_attempts=5, max_total_time=300.0):
breaker = CircuitBreakerWithTimeout(max_total_time)
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if not breaker.should_retry(attempt, e):
raise
delay = calculate_backoff(attempt)
print(f"Attempt {attempt + 1} failed. Retrying in {delay:.2f}s...")
time.sleep(delay)
raise Exception(f"All {max_attempts} attempts failed")
return wrapper
return decorator
Error 3: Missing Rate Limit Header Handling
Problem: Ignoring the Retry-After header and using fixed backoff, causing unnecessary delays or premature retries.
Solution: Parse and respect the Retry-After header from rate limit responses.
# BAD: Ignoring Retry-After header
def bad_retry_handler(response):
if response.status_code == 429:
time.sleep(5) # Fixed delay, ignores server guidance
return retry()
GOOD: Respecting Retry-After header
def good_retry_handler(response):
if response.status_code == 429:
# Try to parse Retry-After header
retry_after = response.headers.get("Retry-After")
if retry_after:
try:
# Could be seconds or HTTP date
wait_seconds = int(retry_after)
except ValueError:
# It's a date, parse it
from email.utils import parsedate_to_datetime
retry_date = parsedate_to_datetime(retry_after)
wait_seconds = (retry_date - datetime.now()).total_seconds()
wait_seconds = max(1, wait_seconds) # At least 1 second
# But add jitter to prevent synchronized retries
wait_seconds = wait_seconds + random.uniform(0, wait_seconds * 0.1)
print(f"Rate limited. Waiting {wait_seconds:.2f}s (server suggested {retry_after})")
time.sleep(wait_seconds)
return retry()
return response
Complete implementation
class RateLimitAwareClient:
"""Client that properly handles rate limit responses."""
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.api_key = api_key
self.session = requests.Session()
def request_with_rate_limit_handling(self, method: str, endpoint: str, **kwargs):
"""Make request with proper rate limit handling."""
headers = kwargs.pop("headers", {})
headers["X-API-KEY"] = self.api_key
for attempt in range(5):
response = self.session.request(
method,
f"{self.base_url}{endpoint}",
headers=headers,
timeout=30,
**kwargs
)
if response.status_code == 200:
return response.json()
elif response.status_code == 429:
# Get server's retry guidance
retry_after = response.headers.get("Retry-After", "60")
try:
wait_time = int(retry_after)
except ValueError:
from email.utils import parsedate_to_datetime
wait_time = (parsedate_to_datetime(retry_after) - datetime.now()).total_seconds()
# Add 10% jitter to prevent synchronized retries
actual_wait = wait_time * random.uniform(1.0, 1.1)
print(f"Rate limited (attempt {attempt + 1}). Waiting {actual_wait:.1f}s...")
time.sleep(actual_wait)
continue
elif 400 <= response.status_code < 500:
# Client error - don't retry
response.raise_for_status()
else:
# Server error - retry with backoff
delay = calculate_backoff(attempt)
time.sleep(delay)
raise Exception("Max retry attempts exceeded")
Performance Comparison: With vs Without HolySheep
Here's a real-world comparison of handling 10,000 order book requests: