Short answer: CCXT is an MIT-licensed library that gives Python one unified API for more than 100 crypto exchanges. You load markets once, call methods such as fetch_ticker, fetch_ohlcv and create_order with unified symbols like BTC/USDT, and pass exchange-specific options in a params dictionary. As of 5 November 2026 the current release is 4.5.92 and WebSocket streaming (CCXT Pro) is included free. A bot built on it still needs testnet trials, precision checks, error handling and risk limits of its own.
CCXT puts one interface over each exchange's signing, symbols, rounding and error codes, which makes it a common starting point for a multi-exchange bot in Python, though differences that matter for real orders remain, as shown below.
Every sample was run on 5 November 2026 with ccxt 4.5.92 on Python 3.11.3 against public endpoints and the Binance Spot Testnet, without API keys, so no order was placed there; the lost-reply paths ran against a local mock server instead. This guide recommends no exchange or strategy; whether you may use an exchange depends on your jurisdiction and its terms. In Turkey, Article 99/A of Capital Markets Law No. 6362 treats activity by foreign-based crypto-asset platforms directed at residents of Turkey as unauthorized service provision. Automated trading can lose money.
How to install CCXT in Python: sync, async and Pro
Install it with pip install ccxt; one package provides sync REST, asyncio REST and WebSocket clients.
As of 5 November 2026 the current PyPI release is 4.5.92, published on 4 November 2026; it needs Python 3.10 or later and is MIT-licensed. The package lists 104 exchange classes in ccxt.exchanges, 76 with WebSocket support, and the Pro manual calls Pro a free part of CCXT. Releases are frequent: pin the tested version.
| Import | Style | Typical use |
|---|---|---|
| import ccxt | Synchronous REST | Scripts, polling bots |
| import ccxt.async_support | asyncio REST | Many exchanges at once |
| import ccxt.pro | asyncio WebSocket | Streaming data and orders |
load_markets: unified symbols and exchange market ids
load_markets() downloads an exchange's market list once and maps unified symbols such as BTC/USDT to the exchange's own ids, precision and limits.
import ccxt
exchanges = {name: getattr(ccxt, name)() for name in ("binance", "kraken", "okx")}
for name, ex in exchanges.items():
ex.load_markets() # one request; cached on the instance
m = ex.market("BTC/USDT") # same unified symbol everywhere
print(f"{name:8} id={m['id']:9} tick={m['precision']['price']:<5} "
f"step={m['precision']['amount']:<6} min_amount={m['limits']['amount']['min']} "
f"min_cost={m['limits']['cost']['min']}")binance id=BTCUSDT tick=0.01 step=1e-05 min_amount=1e-05 min_cost=5.0
kraken id=XBTUSDT tick=0.1 step=1e-08 min_amount=6e-05 min_cost=0.5
okx id=BTC-USDT tick=0.1 step=1e-08 min_amount=1e-05 min_cost=NoneBTC/USDT is BTCUSDT on Binance, XBTUSDT on Kraken and BTC-USDT on OKX, with different tick sizes, step sizes and minimums. All three use the TICK_SIZE precision mode, which the manual says almost all exchanges use: precision values are increments, not decimal counts. Reuse one instance per exchange: the market cache and rate limiter live on it.
fetch_ticker and fetch_ohlcv: CCXT Python examples
Public market data needs no keys: fetch_ticker returns the latest bid, ask and last price, and fetch_ohlcv returns candles as lists of a millisecond timestamp, open, high, low, close and volume.
import ccxt
ex = ccxt.kraken() # public data: no keys needed
t = ex.fetch_ticker("BTC/USDT")
print(t["symbol"], t["bid"], t["ask"])
candles = ex.fetch_ohlcv("BTC/USDT", "1h", limit=5) # [ms, open, high, low, close, volume]
closed = candles[:-1] # the last candle is still forming
print(ex.iso8601(closed[-1][0]), closed[-1][4])
amount = ex.amount_to_precision("BTC/USDT", 0.000123456789) # truncates, returns str
price = ex.price_to_precision("BTC/USDT", t["bid"] * 0.95) # rounds, returns str
lim = ex.market("BTC/USDT")["limits"]
print(amount, price, float(amount) >= lim["amount"]["min"],
float(amount) * float(price) >= (lim["cost"]["min"] or 0))BTC/USDT 80454.5 80454.6
2026-11-05T04:00:00.000Z 80550.0
0.00012345 76431.8 True TrueThree rules from the CCXT manual: the last candle may be incomplete until the next one starts, so drop it before applying your rules; since is a UTC timestamp in milliseconds, and without it each exchange returns its own default range; and exchanges limit how far back short timeframes go, so store candles yourself.
Precision and limits: amount_to_precision and price_to_precision
The last lines above use CCXT's formatting helpers, which round to the market's precision but do not enforce its limits.
- amount_to_precision() truncates and price_to_precision() rounds; both return strings and raise InvalidOrder if the value rounds to zero.
- Minimum and maximum amount, price and cost are in market['limits'], and staying inside them is your job. A None, like OKX's minimum cost above, means unknown, not unlimited.
Hypothetical example. A bot spends 20 USDT on BTC at 76,000 USDT, with a 0.00001 step and a 5 USDT minimum cost: 0.000263… truncates to 0.00026 BTC, a cost of 19.76 USDT, which is valid. At 3 USDT it truncates to 0.00003 BTC, a cost of 2.28 USDT, below the minimum, so the bot must skip the order.
Sandbox mode, rate limits and CCXT exceptions
Switch to an exchange's test network with set_sandbox_mode(True) right after creating the instance, keep the built-in rate limiter on, and handle errors by class: a rate limit can wait and be retried, an exchange error needs a fix first, and a timeout or 5XX on an order means look it up before anything else.
import os
import ccxt
ex = ccxt.binance({"apiKey": os.environ.get("BINANCE_TESTNET_KEY"), # testnet keys only
"secret": os.environ.get("BINANCE_TESTNET_SECRET")})
ex.set_sandbox_mode(True) # must come before any other call
print(ex.urls["api"]["public"])
def place(symbol, side, amount, price, client_id):
try:
return ex.create_order(symbol, "limit", side, amount, price,
{"clientOrderId": client_id})
except (ccxt.RateLimitExceeded, ccxt.DDoSProtection, ccxt.InvalidNonce) as e:
print("refused, retry later:", type(e).__name__)
except ccxt.OperationFailed: # timeout, 5XX: outcome unknown, look it up first
try: # open or already filled: both are found
return ex.fetch_order(None, symbol, {"clientOrderId": client_id})
except ccxt.BaseError as e: # not found yet is not proof that it failed
print("outcome unknown, do not resend:", type(e).__name__)
except ccxt.ExchangeError as e: # also AuthenticationError, InvalidOrder
print("do not retry:", type(e).__name__, str(e))
place("BTC/USDT", "buy", 0.0002, 50000, "demo-001")https://testnet.binance.vision/api/v3
do not retry: AuthenticationError binance requires "apiKey" credential- Sandbox. Only where the exchange runs one, with separate keys. For Binance it points the spot API at testnet.binance.vision, covered in the Binance API Python guide; 4.5.92 also has enable_demo_trading() for Binance's separate demo environment, not combinable with sandbox mode.
- Rate limits. enableRateLimit is on by default and uses a leaky bucket (a rolling window is optional); rateLimit is a pause in milliseconds multiplied by each endpoint's cost (50 for Binance, 1,000 for Kraken in 4.5.92). It cannot see other processes sharing your IP or key.
- Exceptions. NetworkError, under OperationFailed, covers RequestTimeout, RateLimitExceeded, DDoSProtection, ExchangeNotAvailable and InvalidNonce, usually temporary. ExchangeError subclasses such as AuthenticationError, InsufficientFunds, InvalidOrder and BadSymbol fail again unchanged. Binance's -1000 and -1006 answers raised a bare OperationFailed on a local mock, which neither of those two parents catches, so place catches OperationFailed after the clear refusals. Catch a subclass before its parent.
- Unknown outcomes. A RequestTimeout on create_order does not mean the order failed; the manual says to check orders and balance before acting again. fetch_order with the client order ID finds the order whether it is open or already filled, while a search of fetch_open_orders misses a filled one. Not found is not proof of failure either, because the order may arrive late, so look again before sending anything (see crypto exchange API integration). The parameter name is exchange-specific: in 4.5.92, fetch_order reads clientOrderId on Binance, OKX, KuCoin and Bitget and sends it as userref on Kraken, while Bybit's does not read it.
CCXT Pro in Python: install, WebSocket streams and async
CCXT Pro needs no separate install: pip install ccxt includes it, and Python imports it as ccxt.pro. On 4 November 2026 the repository's ccxt.pro list held 76 exchanges, fewer than the 104 REST classes, so check ccxt.pro.exchanges before planning on streams. ccxt.async_support and ccxt.pro run requests to several exchanges concurrently with asyncio, and Pro's watch_* methods wait for the next WebSocket update instead of polling.
import asyncio
import ccxt.pro as ccxtpro
async def watch(ex, n=2):
try:
for _ in range(n):
t = await ex.watch_ticker("BTC/USDT") # waits for the next update
print(f"{ex.id:6} bid={t['bid']} ask={t['ask']}")
finally:
await ex.close() # release the connection
async def main(): # two exchanges, concurrently
await asyncio.gather(watch(ccxtpro.kraken()), watch(ccxtpro.okx()))
asyncio.run(main())kraken bid=80448.4 ask=80448.5
okx bid=80446.5 ask=80446.6
okx bid=80446.5 ask=80446.6
kraken bid=80454.4 ask=80454.5Each await returns the next update from an open connection; Pro also has watch_trades, watch_ohlcv, watch_order_book and private streams such as watch_orders and watch_balance. Always call close(), or CCXT prints a warning. On 5 November 2026, with aiohttp 3.14.5, close() raised an AttributeError in asyncio's SSL layer on all 15 runs on Python 3.11.3 and on none of 9 on 3.10.11, the version behind the output above, so test shutdown on the Python you deploy. Keeping a synchronized depth book is covered in the WebSocket order book guide.
CCXT alternatives: when not to use CCXT
Skip CCXT when you trade one venue and need all of its features, when you only need normalized market data, or when your exchange is not on its list. These CCXT alternatives solve different problems and are not ranked.
| Need | Option | What to check |
|---|---|---|
| Every feature of one exchange | Direct integration with its REST and WebSocket APIs, or its official SDK, such as binance-sdk-spot for Binance or pybit for Bybit; guides for Bybit, OKX, Kraken, Coinbase and Hyperliquid | You own signing, retries and API upgrades |
| Normalized market data only | Cryptofeed, a Python feed handler for exchange data | Its README badge shows an AGPL license and it asks for Python 3.13 or later |
| Backtesting and live trading in one engine | NautilusTrader, with a Rust core and Python bindings | LGPL-3.0; a full engine to learn, with a shorter exchange list |
| An exchange CCXT does not list | Direct integration | The README lists BtcTurk but not Paribu, and no separate Binance TR or OKX TR |
Either way, the strategy and its losses stay yours. For readers in Turkey, an exchange appearing in a library's list says nothing about whether it may serve you.
A tested CCXT Python trading bot skeleton
The skeleton acts once per closed candle, sizes orders to the market's precision and minimum, tags each order with a client order ID derived from the bar, sells on exit what the entry bought, and sends orders only to the Binance Spot Testnet, and only when explicitly enabled.
import os
import ccxt
import pandas as pd
SYMBOL, TIMEFRAME, MAX_COST = "BTC/USDT", "1h", 20.0 # hypothetical cap in USDT
SEND = os.environ.get("BOT_MODE") == "testnet" # default: dry run
ex = ccxt.binance({"apiKey": os.environ.get("BINANCE_TESTNET_KEY"),
"secret": os.environ.get("BINANCE_TESTNET_SECRET")})
ex.set_sandbox_mode(True) # data and orders: Binance Spot Testnet only
ex.load_markets()
min_cost = ex.market(SYMBOL)["limits"]["cost"]["min"] or 0
def send(side, amount, cid):
try:
ex.create_order(SYMBOL, "market", side, float(amount), None, {"clientOrderId": cid})
except ccxt.OperationFailed: # timeout, 5XX: it may have gone through
try:
ex.fetch_order(None, SYMBOL, {"clientOrderId": cid})
except ccxt.BaseError as e: # missing or no answer: never resend on a guess
raise SystemExit(f"status unknown for {cid} ({type(e).__name__}): reconcile first")
def step(state):
rows = ex.fetch_ohlcv(SYMBOL, TIMEFRAME, limit=100)[:-1] # closed bars only
bar, close = rows[-1][0], pd.Series([r[4] for r in rows])
if bar == state.get("bar"):
return "no new closed bar"
fast = close.ewm(span=12, adjust=False).mean().iloc[-1] # hypothetical rule,
slow = close.ewm(span=26, adjust=False).mean().iloc[-1] # not a recommendation
if (fast > slow) == state.get("long", False):
state["bar"] = bar
return f"hold (long={fast > slow})"
if fast > slow: # entry: size from the cap
side, amount = "buy", ex.amount_to_precision(SYMBOL, MAX_COST / close.iloc[-1])
if float(amount) * close.iloc[-1] < min_cost:
state["bar"] = bar
return "below minimum cost, skipped"
else: # exit: what the entry bought; fees can reduce it
side, amount = "sell", state["amount"]
if SEND:
send(side, amount, f"bot-{bar}")
state.update(bar=bar, long=fast > slow, amount=amount) # only after the order went through
return f"{'sent' if SEND else 'dry run'}: {side} {amount}"
state = {}
for _ in range(2): # a real bot loops forever and alerts on errors
print(step(state))Run with no keys on 5 November 2026, it printed dry run: buy 0.00024 on one run and hold (long=False) on a later one, because the first line depends on the latest candles. Each run then printed no new closed bar: the second call found no newer candle, the guard against acting twice on one bar. The bar is recorded only after the order went through. If the reply is lost, the bot looks the order up, and it stops for reconciliation if it cannot confirm the order, because a resend with the same ID could double a filled market order. With BOT_MODE=testnet and testnet keys set, it sends market orders to the testnet. No keys were used here, so the order and lost-reply paths ran only against a local mock server, through ccxt's real request and error code: buy 0.00024 and then sell 0.00024 on a scripted path, one order after a lost reply on a filled entry, and a stop with no resend when the order could not be found.
Before anything like it trades real money, it needs persistent state, an exit amount adjusted for fees, reconciliation with fetch_balance and fetch_open_orders after restarts, an independent risk layer with a kill switch, and alerting, as described in trading bot development and trading bot risk management. Keys belong in a secrets store with trade-only permissions, as in API key security for trading bots.
Where exchange-specific parameters are still needed
Everything outside the unified path goes through exchange-specific params. In 4.5.92, Binance's create_order maps the unified clientOrderId to newClientOrderId and accepts test: True for the spot order-test endpoint. Margin modes, position sides and endpoints without a unified method still need each exchange's documentation. Connecting several venues under one risk layer is the scope of the exchange API integration service.
Frequently asked questions
Is CCXT free for commercial use?
Yes. The MIT license allows commercial use, without warranty; each exchange's API terms still apply.
Is CCXT Pro free, and does it need a separate install?
It is free, and no separate install is needed: the Pro manual calls it a free part of CCXT, pip install ccxt includes it and Python imports it as ccxt.pro. Fewer exchanges support it than the REST classes.
Does CCXT support testnets?
Where the exchange runs one: call set_sandbox_mode(True) right after creating the instance, with keys from the test site.
Why does fetch_ohlcv return fewer candles than I asked for?
Exchanges limit how many candles they return and how far back they go, and periods without trades leave gaps. Page through history with the since argument in milliseconds and store what you fetch.
Does CCXT handle rate limits automatically?
Its built-in limiter is on by default and paces requests per exchange instance. Tune rateLimit to the exchange's published limits and still handle RateLimitExceeded and DDoSProtection.
Sources
- CCXT GitHub repository and README (supported exchanges, MIT license) (opens in a new tab)
- CCXT Manual (markets, precision and limits, sandbox, rate limit, OHLCV, orders, errors) (opens in a new tab)
- CCXT Pro Manual (WebSocket watch methods, Pro is free) (opens in a new tab)
- PyPI: ccxt (release 4.5.92, Python requirement) (opens in a new tab)
- CCXT source: error hierarchy (opens in a new tab)
- CCXT source: Python Binance exchange class (create_order params, demo trading) (opens in a new tab)
- Binance Spot Testnet: General info (opens in a new tab)
- Capital Markets Law No. 6362 (consolidated text, Article 99/A, Turkish) (opens in a new tab)
- pybit on GitHub (official Python connector for Bybit) (opens in a new tab)
- PyPI: binance-sdk-spot (official Binance Spot SDK) (opens in a new tab)
- Cryptofeed on GitHub (README, license badge) (opens in a new tab)
- NautilusTrader on GitHub (README, license) (opens in a new tab)
- CCXT source: python/ccxt/pro/__init__.py (Pro exchange list) (opens in a new tab)
- CCXT source: Python exchange classes (fetch_order parameters) (opens in a new tab)
This guide is general information about software and markets, not investment, legal or tax advice. Automated trading can lose money, past or backtested performance does not guarantee future results, and the rules that apply depend on your jurisdiction and your exchange's terms. Sources, checks and corrections: see the editorial policy.