Binance API: Download Daily Historical Data with Python

·

Cryptocurrency traders, data analysts, and algorithmic developers often require access to high-resolution historical market data to backtest strategies, analyze price movements, or build predictive models. The Binance API provides a powerful and free way to retrieve this data programmatically. In this guide, you’ll learn how to download daily historical candlestick data from Binance using Python, even when the API limits each request to 1,000 records.

We’ll walk through the technical implementation step by step, explain key constraints, and provide a clean, reusable code example that saves data in both JSON and CSV formats.


Understanding Binance Kline/Candlestick Data

The Binance REST API offers access to kline (candlestick) data via the endpoint:

/api/v1/klines

This endpoint returns open, high, low, close (OHLC) prices along with volume and other metadata for specified time intervals.

Key Parameters

👉 Get started with real-time and historical crypto data using a reliable platform.


Why 1,000 Records Isn’t Enough for Daily Data

A single day contains 1,440 minutes. Since the smallest interval is 1 minute, retrieving full daily data would require 1,440 records. However, Binance limits each API call to 1,000 data points — meaning one request isn’t sufficient.

To overcome this limitation, we split the day into two 12-hour chunks:

Each 12-hour period generates 720 records (720 = 60 minutes × 12 hours), well within the 1,000-record limit.

We make two separate API calls and merge the results.


Complete Python Script to Download Daily Data

Below is a fully functional script that downloads 1-minute candlestick data for any given date and symbol.

import datetime
import json
import requests
from urllib.parse import urljoin

class BinanceException(Exception):
    def __init__(self, status_code, data=None):
        self.status_code = status_code
        if data:
            self.code = data.get('code')
            self.msg = data.get('msg')
            message = f"{status_code} [{self.code}] {self.msg}"
        else:
            self.code = None
            self.msg = None
            message = f"status_code={status_code}"
        super().__init__(message)

class BinanceClient:
    BASE_URL = "https://api.binance.com"

    def __init__(self, api_key=None):
        self.API_KEY = api_key

    def download_12h_candles(self, symbol: str, start_time: datetime.datetime, end_time: datetime.datetime):
        PATH = '/api/v1/klines'
        params = {
            'symbol': symbol.upper(),
            'interval': '1m',
            'limit': 1000,
            'startTime': int(start_time.timestamp() * 1000),
            'endTime': int(end_time.timestamp() * 1000)
        }
        headers = {'X-MBX-APIKEY': self.API_KEY} if self.API_KEY else {}
        url = urljoin(self.BASE_URL, PATH)

        response = requests.get(url, headers=headers, params=params)

        if response.status_code == 200:
            return response.json()
        else:
            if response.headers.get('Content-Type') == 'application/json':
                raise BinanceException(status_code=response.status_code, data=response.json())
            else:
                raise BinanceException(status_code=response.status_code)

def download_historical_data(symbol: str, date_str: str):
    """
    Downloads 1-minute kline data for a full day by combining two 12-hour requests.
    Saves output as both .json and .csv (open prices only).
    """
    # Parse input date
    start_date = datetime.datetime.strptime(date_str, '%Y-%m-%d')

    # First 12 hours: 00:00 - 11:59:59
    time1_start = start_date
    time1_end = start_date + datetime.timedelta(hours=12) - datetime.timedelta(seconds=1)

    # Second 12 hours: 12:00 - 23:59:59
    time2_start = start_date + datetime.timedelta(hours=12)
    time2_end = start_date + datetime.timedelta(hours=24) - datetime.timedelta(seconds=1)

    # Initialize client (API key optional for public endpoints)
    client = BinanceClient()

    # Fetch both halves
    data = []
    data.extend(client.download_12h_candles(symbol, time1_start, time1_end))
    data.extend(client.download_12h_candles(symbol, time2_start, time2_end))

    # Sort by timestamp just in case
    data.sort(key=lambda x: x[0])

    # Generate filename
    filename = f"{symbol}_{date_str}"

    # Save as JSON
    with open(f"{filename}.json", 'w') as f:
        json.dump(data, f, indent=2)

    # Save open prices as CSV
    with open(f"{filename}.csv", 'w') as f:
        for row in data:
            f.write(f"{row[1]}\n")  # Open price only

    print(f"✅ Data saved: {filename}.json and {filename}.csv")

How to Use It

download_historical_data('BTCUSDT', '2025-04-05')

This will create two files:


FAQ: Frequently Asked Questions

Q: Do I need an API key to download historical data?

No. Public endpoints like /api/v1/klines do not require authentication. You can omit the API key unless you're accessing private account data.

Q: Can I download more than one day of data?

Yes. You can loop over multiple dates. Just ensure you respect rate limits (e.g., no more than 1,200 requests per minute).

Q: What does each value in the kline array represent?

The response structure is:

[
  [
    1499040000000,      // Open time
    "0.01634790",       // Open price
    "0.80000000",       // High price
    "0.01575800",       // Low price
    "0.01577100",       // Close price
    "148976.11427815",  // Volume
    1499644799999,      // Close time
    "2434.19055334",    // Quote asset volume
    308,                // Number of trades
    "1756.87402397",    // Taker buy base asset volume
    "28.46694368",      // Taker buy quote asset volume
    "17928899.62484339" // Ignore field
  ]
]

Q: Why not use intervals larger than 1 minute?

Larger intervals (like 5m or 1h) return fewer data points per day, so you won’t hit the 1,000-record limit. This workaround is only needed for 1-minute granularity.

Q: Can I store this data in a database instead?

Absolutely. Instead of writing to files, you can insert rows into SQLite, PostgreSQL, or cloud databases like MongoDB or BigQuery for long-term analysis.


Core Keywords for SEO

To improve discoverability and align with search intent, here are the core keywords naturally integrated throughout this article:

These terms reflect common queries from developers and quants looking to work with granular Binance datasets.


Enhance Your Crypto Data Workflow

Whether you're building a trading bot, conducting technical analysis, or training machine learning models, having reliable access to clean historical data is essential.

While Binance provides robust public APIs, managing pagination, time zones, error handling, and file output can become complex at scale.

👉 Explore advanced tools for fetching and processing crypto market data efficiently.


Final Thoughts

Downloading full-day 1-minute candlesticks from Binance requires splitting requests due to API limitations — but with a simple two-call strategy, it’s entirely feasible. The provided Python code is production-ready, modular, and easy to extend for batch processing or integration into larger systems.

By mastering these techniques, you gain direct control over your dataset without relying on third-party providers — saving costs and increasing transparency.

Remember to handle rate limits responsibly and consider caching responses locally to avoid redundant calls.

With accurate historical data in hand, your next breakthrough in algorithmic trading or market research is just a script away.

👉 Start analyzing real-time crypto trends today — no signup required.