Binance API Klines: Fetch Candlestick Data Like a Pro Trader
Master the Binance API klines endpoint to pull candlestick data for trading bots, backtesting strategies, and real-time market analysis with practical code examples.
Master the Binance API klines endpoint to pull candlestick data for trading bots, backtesting strategies, and real-time market analysis with practical code examples.
Klines — short for K-line candlesticks — are the backbone of every technical analysis strategy. Each kline represents price action over a specific time interval: the open, high, low, close, and volume. If you've ever stared at a candlestick chart on Binance or Bybit, you were looking at kline data rendered visually.
The Binance API klines endpoint gives you raw access to this data programmatically. Instead of manually eyeballing charts, you can pull thousands of candles into your code, feed them into indicators like RSI or MACD, and build automated trading systems that react faster than any human. Whether you're backtesting a mean-reversion strategy or building a live signal bot, the klines endpoint is where your data pipeline starts.
Platforms like VoiceOfChain consume this kind of raw market data at scale to generate real-time trading signals — and understanding how the data flows from exchange APIs to actionable alerts gives you a massive edge over traders who only rely on pre-built tools.
The Binance API klines spot endpoint lives at /api/v3/klines — commonly referred to as the Binance API klines v3 endpoint. It's a public GET request, meaning you don't need API keys or authentication to fetch candlestick data. This makes it one of the easiest endpoints to start with if you're new to crypto APIs.
Here's a minimal Binance API klines example that fetches the last 100 one-hour candles for BTC/USDT:
import requests
url = "https://api.binance.com/api/v3/klines"
params = {
"symbol": "BTCUSDT",
"interval": "1h",
"limit": 100
}
response = requests.get(url, params=params)
response.raise_for_status()
klines = response.json()
# Each kline is an array:
# [open_time, open, high, low, close, volume, close_time,
# quote_asset_volume, num_trades, taker_buy_base_vol,
# taker_buy_quote_vol, ignore]
for k in klines[-3:]:
print(f"Open: {k[1]}, High: {k[2]}, Low: {k[3]}, Close: {k[4]}, Volume: {k[5]}")
The response returns an array of arrays. Each inner array contains 12 elements — from the opening timestamp to volume breakdowns. Unlike some exchanges that return named JSON fields (OKX does this, for instance), Binance uses positional arrays for kline candlestick data, so you'll need to map indices to field names yourself.
| Index | Field | Example |
|---|---|---|
| 0 | Open time (ms) | 1704067200000 |
| 1 | Open price | 42150.00 |
| 2 | High price | 42380.50 |
| 3 | Low price | 42010.00 |
| 4 | Close price | 42290.75 |
| 5 | Volume | 1523.847 |
| 6 | Close time (ms) | 1704070799999 |
| 7 | Quote asset volume | 64382915.23 |
| 8 | Number of trades | 48291 |
| 9 | Taker buy base volume | 812.334 |
| 10 | Taker buy quote volume | 34321567.89 |
| 11 | Unused | 0 |
The Binance API klines interval parameter controls the candle timeframe. You can go from 1-second candles all the way up to monthly — which is more granularity than most exchanges offer. Bybit and OKX support similar ranges, but Binance's 1s interval is particularly useful for high-frequency strategies.
| Interval Code | Timeframe |
|---|---|
| 1s | 1 second |
| 1m | 1 minute |
| 3m, 5m, 15m, 30m | Minutes |
| 1h, 2h, 4h, 6h, 8h, 12h | Hours |
| 1d | 1 day |
| 3d | 3 days |
| 1w | 1 week |
| 1M | 1 month |
The Binance API klines limit parameter caps how many candles you get per request. The default is 500 and the maximum is 1000. If you need more historical data — say, a year of 1h candles for backtesting — you'll need to paginate using the startTime and endTime parameters.
import requests
import time
def fetch_all_klines(symbol, interval, start_ts, end_ts):
"""Paginate through Binance klines to fetch large date ranges."""
url = "https://api.binance.com/api/v3/klines"
all_klines = []
current_start = start_ts
while current_start < end_ts:
params = {
"symbol": symbol,
"interval": interval,
"startTime": current_start,
"endTime": end_ts,
"limit": 1000
}
try:
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
time.sleep(2)
continue
if not data:
break
all_klines.extend(data)
# Move start to 1ms after the last candle's close time
current_start = data[-1][6] + 1
# Respect rate limits
time.sleep(0.2)
return all_klines
# Fetch 6 months of daily candles
import datetime
start = int(datetime.datetime(2025, 10, 1).timestamp() * 1000)
end = int(datetime.datetime(2026, 4, 1).timestamp() * 1000)
klines = fetch_all_klines("ETHUSDT", "1d", start, end)
print(f"Fetched {len(klines)} daily candles")
Always add a small delay between paginated requests. Hitting the Binance klines API rate limit (currently 2400 request weight per minute for the IP) will get your IP temporarily banned. The /api/v3/klines endpoint has a weight of 2 per call, so you can safely make around 1000 requests per minute — but bursting through them all at once is asking for trouble.
Raw arrays are fine for quick scripts, but any serious analysis or backtesting needs structured data. Converting Binance API kline data into a pandas DataFrame takes three lines and makes everything downstream — plotting, indicator calculation, signal generation — dramatically easier.
import pandas as pd
import requests
# Fetch 500 4h candles for SOL/USDT
resp = requests.get("https://api.binance.com/api/v3/klines", params={
"symbol": "SOLUSDT",
"interval": "4h",
"limit": 500
})
data = resp.json()
# Build DataFrame with proper column names and types
columns = [
"open_time", "open", "high", "low", "close", "volume",
"close_time", "quote_volume", "trades", "taker_buy_base",
"taker_buy_quote", "ignore"
]
df = pd.DataFrame(data, columns=columns)
# Convert types
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
df["close_time"] = pd.to_datetime(df["close_time"], unit="ms")
for col in ["open", "high", "low", "close", "volume", "quote_volume"]:
df[col] = df[col].astype(float)
# Drop the unused column
df.drop(columns=["ignore"], inplace=True)
df.set_index("open_time", inplace=True)
print(df[["open", "high", "low", "close", "volume"]].tail())
# Quick example: calculate 20-period SMA
df["sma_20"] = df["close"].rolling(window=20).mean()
print(f"\nCurrent close: {df['close'].iloc[-1]:.2f}")
print(f"SMA(20): {df['sma_20'].iloc[-1]:.2f}")
This pattern — fetch, parse, analyze — is the foundation of every crypto trading bot. Whether you're running a simple moving average crossover or a machine learning model, the pipeline starts with clean kline data from the API. Tools like VoiceOfChain build on this same data layer to deliver real-time signals that traders can act on without writing their own infrastructure.
Production-grade code needs to handle failures gracefully. The Binance API returns specific error codes that tell you exactly what went wrong. A 429 status means you've hit the rate limit. A 418 means your IP has been auto-banned for repeated violations. A -1121 error means the symbol you requested doesn't exist.
Here's a robust wrapper that handles the common failure modes you'll encounter when pulling Binance API klines documentation recommends watching for:
import requests
import time
from dataclasses import dataclass
@dataclass
class KlineRequest:
symbol: str
interval: str
limit: int = 500
def fetch_klines_safe(req: KlineRequest, max_retries: int = 3):
"""Fetch klines with retry logic and rate limit handling."""
url = "https://api.binance.com/api/v3/klines"
params = {"symbol": req.symbol, "interval": req.interval, "limit": req.limit}
for attempt in range(max_retries):
try:
resp = requests.get(url, params=params, timeout=10)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
if resp.status_code == 418:
print("IP banned. Stop all requests and wait.")
return None
error = resp.json()
print(f"API error {error.get('code')}: {error.get('msg')}")
return None
except requests.exceptions.Timeout:
print(f"Timeout on attempt {attempt + 1}/{max_retries}")
time.sleep(2 ** attempt)
except requests.exceptions.ConnectionError:
print(f"Connection error. Retrying in {2 ** attempt}s...")
time.sleep(2 ** attempt)
print("All retries exhausted.")
return None
# Usage
klines = fetch_klines_safe(KlineRequest(symbol="BTCUSDT", interval="15m", limit=200))
if klines:
print(f"Got {len(klines)} candles. Latest close: {klines[-1][4]}")
Different exchanges handle rate limits differently. Binance uses IP-based weight limits, while Bybit uses a per-endpoint rate system and OKX tracks limits per API key. If you're pulling data from multiple exchanges simultaneously, you'll need separate rate limiter logic for each one.
The endpoint we've been using — /api/v3/klines — is specifically the Binance API klines spot endpoint. If you need futures data, you'll hit a different base URL: fapi.binance.com for USD-M futures and dapi.binance.com for COIN-M futures. The parameters and response format are identical, which makes switching between them trivial.
| Market | Base URL | Endpoint |
|---|---|---|
| Spot | api.binance.com | /api/v3/klines |
| USD-M Futures | fapi.binance.com | /fapi/v1/klines |
| COIN-M Futures | dapi.binance.com | /dapi/v1/klines |
If you're comparing price action across exchanges — say, checking whether BTC/USDT candles on Binance diverge from the same pair on KuCoin or Bitget — you can use the same data-fetching pattern with adjusted base URLs and minor parameter differences. Most major exchanges have adopted a similar klines endpoint structure, though field ordering and naming vary.
One practical tip: Binance spot klines sometimes show wicks that don't appear on futures charts for the same pair, because spot and perp order books have different liquidity profiles. If your strategy depends on exact wick levels, make sure you're pulling data from the right market type.
The Binance API klines endpoint is deceptively simple — a single GET request returns everything you need to build charts, calculate indicators, train models, or generate trading signals. The real skill is in what you do after the data lands in your code.
Start with the basic fetch, structure it into a DataFrame, layer on your indicators, and you've got the foundation of a trading system. Whether you're comparing candle patterns across Binance and OKX, building a backtesting engine, or feeding data into a platform like VoiceOfChain for signal generation, the klines endpoint is your primary data source.
One last piece of advice: don't just pull data — validate it. Check for gaps in timestamps, compare volumes against what you see on the exchange UI, and test your code against known historical events. Clean data in means reliable signals out. The API is just the starting line.