El mecanismo central del límite de frecuencia de la API de Binance es la Cubeta de Pesos (Weight Bucket): cada IP tiene un máximo de 6000 pesos por minuto (en Spot). Cada endpoint descuenta entre 1 y 100 puntos de peso según su complejidad; si excedes el límite, recibirás un error 429 Too Many Requests. Las infracciones repetidas elevarán el error a 418, lo que resulta en un baneo de IP que puede durar desde 2 minutos hasta 3 días. Este artículo explica en cuatro pasos (cálculo de pesos, monitoreo de encabezados, control del cliente y alternativas WebSocket) cómo mantener tu estrategia estable sin activar el control de riesgos. Si aún no tienes una llave API, completa el KYC en el sitio oficial de Binance; si no tienes cuenta, puedes realizar un registro gratuito.
I. Las tres dimensiones del límite de frecuencia
Binance aplica tres tipos de restricciones independientes para una misma IP o llave API:
| Dimensión | Límite Spot | Límite Futuros | Respuesta por infracción |
|---|---|---|---|
| Peso de la solicitud (REQUEST_WEIGHT) | 6000 / minuto | 2400 / minuto | 429 |
| Número de órdenes (ORDERS) | 100 / 10 seg; 200,000 / día | 300 / 10 seg; 1200 / minuto | 429 |
| Número de conexiones (RAW_REQUESTS) | 61,000 / 5 minutos | 61,000 / 5 minutos | 429 |
| Baneo de IP | Infracciones continuas | Infracciones continuas | 418 |
Concepto clave: Aunque el peso de un endpoint de orden sea solo 1, también consume la cubeta de ORDERS; si cualquiera de las dos cubetas alcanza su límite, serás restringido.
II. Consultar la cuota de peso actual
Puedes obtener la cuota en tiempo real de tu cuenta a través del campo rateLimits en GET /api/v3/exchangeInfo:
curl -s "https://api.binance.com/api/v3/exchangeInfo" | \
jq '.rateLimits'
Respuesta:
[
{"rateLimitType": "REQUEST_WEIGHT", "interval": "MINUTE", "intervalNum": 1, "limit": 6000},
{"rateLimitType": "ORDERS", "interval": "SECOND", "intervalNum": 10, "limit": 100},
{"rateLimitType": "ORDERS", "interval": "DAY", "intervalNum": 1, "limit": 200000},
{"rateLimitType": "RAW_REQUESTS", "interval": "MINUTE", "intervalNum": 5, "limit": 61000}
]
Las cuentas de nivel VIP pueden solicitar un aumento del límite de peso, pero el estándar de 6000 es suficiente para el 90% de las estrategias.
III. Tabla comparativa de pesos de los endpoints comunes
| Endpoint | Peso | Descripción |
|---|---|---|
| GET /api/v3/ping | 1 | Prueba de conectividad |
| GET /api/v3/time | 1 | Hora del servidor |
| GET /api/v3/exchangeInfo | 20 | Reglas de trading (cachear 1 hora es suficiente) |
| GET /api/v3/ticker/price (un solo símbolo) | 1 | Precio de un solo par |
| GET /api/v3/ticker/price (todos) | 4 | Obtener todos los precios a la vez |
| GET /api/v3/ticker/24hr (un solo símbolo) | 1 | Estadísticas 24h de un solo par |
| GET /api/v3/ticker/24hr (todos) | 80 | Estadísticas de todos los pares |
| GET /api/v3/depth limit=5/10/20/50/100 | 1 | Libro de órdenes |
| GET /api/v3/depth limit=500 | 5 | Libro de órdenes |
| GET /api/v3/depth limit=1000 | 10 | Libro de órdenes |
| GET /api/v3/depth limit=5000 | 50 | Libro de órdenes (usar con precaución) |
| GET /api/v3/klines | 2 | Velas (K-lines) |
| GET /api/v3/historicalTrades | 5 | Historial de operaciones |
| GET /api/v3/account | 20 | Saldo de la cuenta |
| GET /api/v3/openOrders (un solo símbolo) | 6 | Órdenes abiertas actuales |
| GET /api/v3/openOrders (todos) | 80 | Todas las órdenes abiertas |
| GET /api/v3/allOrders | 20 | Historial de órdenes |
| POST /api/v3/order | 1 | Colocar orden |
| DELETE /api/v3/order | 1 | Cancelar orden |
| DELETE /api/v3/openOrders | 1 | Cancelar todas las órdenes |
Trampa de rendimiento: No llames en bucle a ticker/24hr para cada símbolo individual; usa el endpoint masivo sin parámetros para reducir el peso de 300 símbolos × 1 = 300 a solo 80.
IV. Leer encabezados de respuesta para un límite de frecuencia adaptativo
Cada respuesta de una petición REST devuelve el peso utilizado actualmente. El cliente debe leerlo y ajustarse dinámicamente:
import requests, time
BASE_URL = "https://api.binance.com"
class RateLimiter:
def __init__(self, max_weight=6000, safety_ratio=0.8):
self.max_weight = max_weight
self.safety = safety_ratio # Solo usar el 80% para prevenir errores de arranque en frío
self.used_weight = 0
def update_from_headers(self, headers: dict):
used = headers.get("X-MBX-USED-WEIGHT-1m")
if used:
self.used_weight = int(used)
def should_wait(self) -> float:
"""Devuelve los segundos sugeridos de espera; 0 significa que se puede llamar directamente"""
threshold = self.max_weight * self.safety
if self.used_weight >= threshold:
# Estimación de segundos hasta el reinicio del próximo minuto
return 60 - (int(time.time()) % 60)
return 0
limiter = RateLimiter()
def safe_get(path, params=None):
wait = limiter.should_wait()
if wait > 0:
print(f"[Límite] {limiter.used_weight} de peso usado, pausando {wait}s")
time.sleep(wait)
r = requests.get(f"{BASE_URL}{path}", params=params, timeout=10)
limiter.update_from_headers(r.headers)
return r.json()
# Uso
data = safe_get("/api/v3/ticker/24hr")
print(f"Ocupación de peso actual: {limiter.used_weight}/6000")
V. Límite de frecuencia con cubeta de tokens (Control activo del cliente)
Una práctica más segura que mirar los encabezados de forma pasiva es usar una Cubeta de Tokens (Token Bucket) en el cliente:
import time, threading
class TokenBucket:
def __init__(self, capacity=6000, refill_per_sec=100):
self.capacity = capacity
self.tokens = capacity
self.refill = refill_per_sec # 6000/60 = 100 por segundo
self.last = time.time()
self.lock = threading.Lock()
def acquire(self, cost=1):
with self.lock:
now = time.time()
elapsed = now - self.last
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill)
self.last = now
if self.tokens < cost:
wait = (cost - self.tokens) / self.refill
time.sleep(wait)
self.tokens = 0
else:
self.tokens -= cost
bucket = TokenBucket(capacity=6000, refill_per_sec=100)
def call(path, weight):
bucket.acquire(weight)
return requests.get(f"{BASE_URL}{path}").json()
# Uso combinado al colocar una orden (peso 1) y consultar saldo (peso 20)
call("/api/v3/ticker/price", 1)
call("/api/v3/account", 20)
VI. Manejo correcto de los errores 429 y 418
1. Recibir un 429
def request_with_retry(method, url, **kwargs):
for attempt in range(3):
r = requests.request(method, url, **kwargs)
if r.status_code == 429:
retry_after = int(r.headers.get("Retry-After", 60))
print(f"Límite activado, durmiendo {retry_after}s")
time.sleep(retry_after)
continue
if r.status_code == 418:
print("¡IP baneada, la aplicación debe detenerse!")
raise SystemExit(1)
return r
raise Exception("Se superó el número máximo de reintentos")
El encabezado Retry-After indica los segundos exactos de espera; simplemente aplica un sleep.
2. Recibir un 418
El error 418 es una advertencia grave: Seguir enviando peticiones después de un 429 resultará en un baneo de IP que comienza en 2 minutos y puede llegar a 3 días en casos extremos. Si recibes este error, debes detener todas las peticiones inmediatamente y esperar al menos el tiempo indicado en Retry-After antes de reanudar.
VII. Alternativa con WebSocket: consumo de peso casi nulo
Mientras que REST consume peso con cada extracción de datos, WebSocket consume peso solo una vez al establecer la conexión; el envío de datos en tiempo real no consume peso:
import json, websocket
def on_message(ws, message):
data = json.loads(message)
print(f"{data['s']} Precio: {data['c']}, Vol 24h: {data['v']}")
ws = websocket.WebSocketApp(
"wss://stream.binance.com:9443/stream?streams=btcusdt@ticker/ethusdt@ticker",
on_message=on_message
)
ws.run_forever()
Comparativa de costos: Consultar el ticker de 10 pares cada segundo vía REST = 600 veces/minuto × peso 1 = 600 pesos; suscripción vía WebSocket = 0 pesos.
VIII. Sugerencias prácticas para la optimización de pesos
- Cachear exchangeInfo: Actualizarlo una vez por hora es suficiente; hacerlo más seguido solo desperdicia 20 de peso cada vez.
- Priorizar consultas masivas: Obtener todos los datos con
/ticker/24hrsin parámetros ahorra un 90% de peso comparado con consultar cada símbolo individualmente. - Limitar la profundidad del libro: Para órdenes limitadas, un
limit=20es suficiente (peso 1); evita solicitar 5000 niveles. - Usar userDataStream para el estado de órdenes: Es más eficiente y consume cero peso comparado con hacer
GET /orderperiódicamente. - Cancelar todas las órdenes masivamente: El endpoint
DELETE /openOrders?symbol=BTCUSDTtiene un peso de 1, siendo más ahorrativo que cancelar una por una. - Límite de frecuencia por tramos horarios: Las 00:00, 08:00 y 16:00 UTC son ventanas de liquidación donde el consumo de peso es más tenso; ajusta tu estrategia para evitar esos picos.
IX. Preguntas frecuentes FAQ
P1: ¿El peso se calcula por IP o por llave API?
R: Principalmente por IP. Varias llaves bajo una misma IP comparten la cubeta de pesos; cambiar de IP puede evadir el límite de IP, pero las restricciones de órdenes se siguen aplicando por cuenta. No se recomienda usar proxies públicos para cambiar de IP, ya que Binance puede detectarlo.
P2: ¿Qué diferencia hay entre X-MBX-USED-WEIGHT y X-MBX-USED-WEIGHT-1m?
R: X-MBX-USED-WEIGHT-1m es el recomendado oficialmente e indica la ventana de 1 minuto; X-MBX-USED-WEIGHT es un campo heredado y su valor es idéntico. Guíate por el que tiene el sufijo -1m.
P3: Me han dado un 418, ¿cuánto debo esperar?
R: 2 minutos por la primera infracción, pudiendo escalar a 5/15/60 minutos y hasta 3 días en casos extremos. El encabezado Retry-After da el tiempo exacto. Optimiza tu código inmediatamente tras la recuperación para evitar infracciones repetidas y bloqueos acumulativos.
P4: ¿Cómo unificar el cálculo de pesos en llamadas concurrentes multihilo?
R: Usa una cubeta de tokens global a nivel de proceso (ver sección V) o una cubeta distribuida en Redis (necesario para despliegues en múltiples máquinas). Un threading.Lock es suficiente para una sola máquina.
P5: ¿Son iguales las reglas de peso en la Testnet (testnet.binance.vision)?
R: Las reglas de peso en la Testnet son más relajadas (usualmente 10 veces superiores a la Mainnet), pero la estructura de las reglas es idéntica. No evalúes el rendimiento de la Mainnet basándote en la Testnet; valida siempre con poco flujo en la Mainnet antes del despliegue total.
Después de revisar las estrategias de límite de frecuencia, vuelve a la navegación de categorías para entrar en «Integración API» y ver tutoriales sobre WebSocket y firmas.