Opening checkout

Platforms and brokers7 min read

CCXT Python Tutorial: One API for Crypto Exchanges

Install CCXT, connect exchange API keys, fetch prices and order books, place orders and handle rate limits across Binance, Coinbase, Kraken and Bybit.

By QuantVPS Team

Published
Updated

CCXT is an open-source library that gives you one Python API for more than 100 crypto exchanges: fetch_ticker(), fetch_order_book() and create_order() work the same way on Binance, Coinbase, Kraken and Bybit. Install it with pip install ccxt, create an exchange object with your API keys, and the library handles signing, symbols and rate limiting for you (checked on 10 Oct 2026).

This tutorial covers installation, keys, market data, orders, rate limits, async code and WebSockets, with a sandbox by default and live orders only behind an explicit --live flag.

Keep your platform running while you're away.

What CCXT Is

CCXT (CryptoCurrency eXchange Trading library) wraps each exchange's own API in a common set of methods and data structures. On 10 Oct 2026 its GitHub README listed 103 cryptocurrency exchanges and 9 prediction market exchanges, with libraries for Python, JavaScript, PHP, C#, Go and Java. For Binance's own SDK, keys and weight limits, see our Binance API guide.

Exchange CCXT id Sandbox in CCXT 4.5.85
Binance binance (binanceus for Binance.US) Yes
Coinbase Advanced coinbase No
Kraken kraken No
Bybit bybit Yes

The unified API covers the common jobs: markets, tickers, candles, order books, balances and orders. Anything an exchange offers beyond that is still reachable through its implicit API methods or extra params, but that code no longer ports between exchanges. Each exchange object has a has dictionary that says which unified methods it supports, such as exchange.has["fetchOHLCV"].

Six CCXT methods most trading bots use: load_markets loads symbols, precision and limits once per instance; fetch_ticker returns the last price, bid and ask; fetch_order_book returns bids and asks; fetch_ohlcv returns candles as time, open, high, low, close and volume; create_order places market or limit orders with the same arguments on every exchange; watch_ticker streams live updates over WebSocket with ccxt.pro.

The same method names work on every exchange CCXT supports, where the exchange offers the feature.

Installing CCXT

Install from PyPI. The current ccxt package requires Python 3.10 or later (the README still says 3.7.0); the examples here were run with CCXT 4.5.85 on Python 3.12.

pip install ccxt

One package gives you three ways in: ccxt for normal synchronous code, ccxt.async_support for asyncio, and ccxt.pro for WebSocket streams. CCXT Pro used to be a paid add-on; the manual now calls it "a free part of CCXT".

Connecting Your Exchange API Keys

Create a key in the API section of your exchange account settings, then pass it to the exchange constructor. Most exchanges need an apiKey and a secret; some also want a password (passphrase) or a uid, and requiredCredentials tells you which.

import os
import ccxt

exchange = ccxt.kraken({
    "apiKey": os.environ["EXCHANGE_API_KEY"],
    "secret": os.environ["EXCHANGE_SECRET"],
})
print(exchange.requiredCredentials)    # which fields this exchange needs
exchange.check_required_credentials()  # raises AuthenticationError if any are missing
print(exchange.fetch_balance()["total"])

A few rules save a lot of debugging:

  • One key per program. The CCXT manual says not to share a key pair between scripts, threads or other software running at the same time. Exchanges track a rising nonce per key, and a second program can push it out of order, which shows up as InvalidNonce errors.
  • Least permission. Give a bot trade permission only, never withdrawals, and restrict the key to your server's IP address where the exchange allows it.
  • Secrets out of code. Read keys from environment variables or a config file that only the owner can read.

Fetching Tickers, OHLCV Candles and Order Books

Public market data needs no key on most exchanges. Load markets once, then call the fetch methods with a unified symbol such as BTC/USD.

import ccxt

exchange = ccxt.kraken()          # public data needs no keys
exchange.load_markets()           # cache markets once per instance
symbol = "BTC/USD"

ticker = exchange.fetch_ticker(symbol)
print("last", ticker["last"], "bid", ticker["bid"], "ask", ticker["ask"])

candles = exchange.fetch_ohlcv(symbol, timeframe="1h", limit=24)
for ts, open_, high, low, close, volume in candles[-3:]:
    print(exchange.iso8601(ts), close)

book = exchange.fetch_order_book(symbol, limit=10)
print("best bid", book["bids"][0], "best ask", book["asks"][0])

fetch_ohlcv() returns a list of [timestamp, open, high, low, close, volume] rows with timestamps in milliseconds. fetch_order_book() returns bids and asks as [price, amount] pairs, best price first. If you skip load_markets(), CCXT loads markets on your first call, which makes that call slower and costs an extra request.

Symbols follow one format everywhere: BASE/QUOTE for spot, and BASE/QUOTE:SETTLE for perpetual swaps (for example BTC/USDT:USDT). CCXT maps them to each exchange's own market IDs.

Placing Orders

Every exchange takes the same call: create_order(symbol, type, side, amount, price), where type is market or limit and amount is usually in the base currency. The script below places a small limit buy 5% under the last price, on Bybit's testnet unless you pass --live.

import os
import sys

import ccxt

LIVE = "--live" in sys.argv       # sandbox unless you pass --live

exchange = ccxt.bybit({
    "apiKey": os.environ["EXCHANGE_API_KEY"],
    "secret": os.environ["EXCHANGE_SECRET"],
})
if not LIVE:
    exchange.set_sandbox_mode(True)   # must come before any other call

symbol = "BTC/USDT"
try:
    exchange.load_markets()
    price = exchange.fetch_ticker(symbol)["last"] * 0.95   # 5% under the market
    order = exchange.create_order(
        symbol, "limit", "buy",
        exchange.amount_to_precision(symbol, 0.001),
        exchange.price_to_precision(symbol, price),
    )
    print(order["id"], order["status"])
except ccxt.InsufficientFunds as e:
    print("not enough balance:", e)
except ccxt.NetworkError as e:          # timeouts, rate limits, maintenance
    print("temporary problem, try again later:", e)
except ccxt.ExchangeError as e:         # rejected: bad symbol, bad keys, bad order
    print("exchange rejected the request:", e)

set_sandbox_mode(True) switches the exchange to its test API and must come straight after you create the object. Only some exchanges have one: of our four, Binance and Bybit do and Coinbase and Kraken do not, so on those two test with read-only keys and tiny sizes. amount_to_precision() and price_to_precision() round your numbers to the market's rules, which avoids many rejected orders.

Errors come in two families. NetworkError covers temporary problems (timeouts, maintenance, RateLimitExceeded, DDoSProtection) that a retry may fix. ExchangeError covers rejections, such as AuthenticationError, InsufficientFunds and InvalidOrder, that will fail again until you change the request.

Locations

Trade beside your market. 8 datacenters, 3 continents.

Pick the location closest to what you trade, not to where you live.

Compare locations
A dot-matrix world map pins every QuantVPS location (Chicago, New York, London, Dublin, Amsterdam, Frankfurt, Tokyo, Singapore). Typical routes: Chicago to CME 0.52 ms, New York to NYSE 0–2 ms, London to FX 0–10 ms.

Rate Limits and Async Code

CCXT throttles requests for you: enableRateLimit is on by default, and each exchange instance waits rateLimit milliseconds times the cost of each request. The defaults are deliberately cautious, so a busy bot may tune them to the exchange's published limits.

Exchange Default rateLimit in CCXT 4.5.85 (ms)
Binance 50
Coinbase Advanced 34
Kraken 1,000
Bybit 20

The rate limiter lives inside each exchange object, so create one instance per exchange and reuse it. Creating a new object for every call resets the limiter and reloads markets each time, which the CCXT manual names as a common way to get banned. In async code, CCXT queues up to 1,000 pending requests per instance by default.

To query several exchanges at once, use ccxt.async_support and asyncio.gather(). Close each exchange when you finish:

import asyncio
import ccxt.async_support as ccxt

async def last_price(exchange, symbol):
    ticker = await exchange.fetch_ticker(symbol)
    return exchange.id, ticker["last"]

async def main():
    exchanges = [ccxt.kraken(), ccxt.coinbase()]
    try:
        results = await asyncio.gather(
            *(last_price(e, "BTC/USD") for e in exchanges)
        )
        for name, price in results:
            print(name, price)
    finally:
        for e in exchanges:
            await e.close()

asyncio.run(main())

WebSocket Streams with ccxt.pro

For live prices, ccxt.pro replaces each fetch_* call with a watch_* call that waits for the next update over a WebSocket. Each call returns the latest state, so you loop on it.

import asyncio
import ccxt.pro as ccxtpro

async def main():
    exchange = ccxtpro.kraken()
    try:
        for _ in range(5):        # a bot would loop forever
            ticker = await exchange.watch_ticker("BTC/USD")
            print(ticker["symbol"], ticker["bid"], ticker["ask"])
    finally:
        await exchange.close()

asyncio.run(main())

Private streams work the same way with keys: watch_balance(), watch_orders() and watch_my_trades(). Check exchange.has["watchTicker"] and similar flags first, because not every exchange supports every stream.

Running CCXT Bots 24/7

Crypto markets never close, so a bot needs a machine that never sleeps. Run it as a service that restarts after a crash, reuse one exchange object for the life of the process, and treat NetworkError as a reason to wait and retry rather than exit.

  • Log every order ID returned by create_order(), so you can check fills after a restart.
  • Reload markets now and then, because exchanges list, delist and pause symbols.
  • Watch the clock: signed requests carry timestamps, and a drifting system clock causes rejections.

Place the server close to the exchange you trade. QuantVPS runs crypto VPS plans in Tokyo for Binance and Bybit (a published 0 to 1ms route), London for Kraken and New York for Coinbase, each with a dedicated IP for API key whitelisting. Every plan includes 99.999% uptime, always-on DDoS protection with advanced filtering and automatic off-site backups, from $29.99 a month (checked on 10 Oct 2026). To test rules before trading them, see our guide to Python backtesting libraries.

Order flow

Built for scalpers. Heatmaps on dedicated resources.

Order-flow heatmaps and DOM ladders run on dedicated CPU, memory and NVMe, so your charts keep responding during volatility.

See the dedicated hardware
Order flow through a news release: resting bids and offers paint in as a heatmap beside the DOM ladder, then a burst of buys takes the offer wall. Illustrative; prices are decorative.

FAQs

Is CCXT free?

Yes. CCXT is open source under the MIT license, and the WebSocket part, CCXT Pro, is now included for free. You still pay each exchange's normal trading fees.

Does CCXT support WebSockets?

Yes, through ccxt.pro, which ships in the same package. Use watch_ticker(), watch_order_book(), watch_ohlcv() and the private watch_orders() and watch_balance() on exchanges that support them.

Why do I get InvalidNonce errors in CCXT?

Usually because two programs, or two exchange objects, share one API key. Give each program its own key pair and reuse one exchange object per key.

Can CCXT trade futures and perpetual swaps?

Yes, on exchanges that offer them. Swap symbols include the settle currency, such as BTC/USDT:USDT, and amounts may be in contracts, so check the market's contractSize before you size an order.

How do I test CCXT without real money?

Call set_sandbox_mode(True) right after creating the exchange, on exchanges that have a sandbox (Binance and Bybit among them). Use the exchange's testnet keys, not your live keys.

Disclaimer: QuantVPS does not represent, guarantee, support, or endorse any third-party brands, products, or services mentioned in this article. All brand references are for informational purposes only. This information does not constitute a recommendation to trade futures or any other financial instruments. All trading decisions are made at your own discretion. Please be aware that futures trading involves significant risk of loss, and past performance does not guarantee future results. Read our full Brand Non-Endorsement Disclaimer.

Risk Disclosure: QuantVPS does not provide financial, investment, or trading advice. Trading involves substantial risk of loss and is not suitable for every investor. Past performance is not indicative of future results. You should consult a qualified financial advisor before making any trading decisions. Read our full Trading Disclaimer.