Home › Guides › Trading bots & code
Crypto trading bot with CCXT: a safe first build in Python
CCXT is a free Python library that talks to about 100 crypto exchanges with the same commands. That makes a first bot surprisingly short. Here's one — with the safety parts written first.
What CCXT is
Every crypto exchange has its own API (the door programs use to read prices and send orders). Each one is a little different.
CCXT smooths that out. You learn one set of commands — fetch_ohlcv for candles, fetch_balance for your balance, create_order for orders — and they work across many exchanges.
It's free, open source, and available for Python and JavaScript. Install it with pip install ccxt pandas.
Before any code: 4 safety steps
- Use a test version if you can. Some exchanges run a test network (sandbox) with fake funds. CCXT can switch to it with one line. Not every exchange has one.
- Make API keys with trading only — withdrawals OFF. Then a stolen key can't move your coins out.
- Turn on IP whitelisting if the exchange offers it, so the key only works from your server.
- Keep keys out of the code. Put them in environment variables. Never paste them into a script, a chat or a shared file.
More: API key security for trading bots.
Step 1: Connect and read candles
Example code for learning — test it on paper first; it is not a ready-made trading system.
import os
import ccxt
import pandas as pd
exchange = ccxt.binance({
"apiKey": os.environ["EXCHANGE_API_KEY"], # keys live in the environment
"secret": os.environ["EXCHANGE_API_SECRET"],
"enableRateLimit": True, # wait between requests
})
exchange.set_sandbox_mode(True) # test version; errors if none
exchange.load_markets()
SYMBOL = "BTC/USDT"
candles = exchange.fetch_ohlcv(SYMBOL, "1h", limit=200)
df = pd.DataFrame(candles, columns=["time", "open", "high", "low", "close", "volume"])
df["time"] = pd.to_datetime(df["time"], unit="ms", utc=True)
df = df.iloc[:-1] # drop the candle still forming
print(df.tail(3))
Line by line:
- ccxt.binance({...}) creates a connection to one exchange. We use Binance here only because it runs a public spot test network; Binance.com isn't available to US residents. Swap in an exchange you can use — the rest of the code stays the same.
- os.environ[...] reads the keys from the environment. If they're missing, the script stops instead of running half-set-up.
- enableRateLimit makes CCXT wait between requests, so the exchange doesn't block you for asking too fast.
- set_sandbox_mode(True) switches to the test network. Test networks usually need their own separate keys. If the exchange has no test version, CCXT raises an error — good. Don't "fix" it by going live.
- load_markets() downloads the list of symbols and their rules (minimum order sizes, decimal places).
- fetch_ohlcv returns candles: time, open, high, low, close, volume. "1h" is the candle size; limit=200 asks for the last 200.
- iloc[:-1] drops the last row, because the current candle isn't finished. Deciding on a half-built candle is a common bug.
Step 2: Size from the stop
Crypto moves fast, and it trades 24/7, weekends included. Size matters even more here.
import logging
logging.basicConfig(filename="bot.log", level=logging.INFO, format="%(asctime)s %(message)s")
DRY_RUN = True # True = log only, no orders
def size_from_stop(equity, risk_pct, entry, stop):
risk_money = equity * risk_pct / 100
per_coin = entry - stop # loss per 1 BTC if stopped
if per_coin <= 0:
return 0.0
amount = float(exchange.amount_to_precision(SYMBOL, risk_money / per_coin))
min_amount = exchange.market(SYMBOL)["limits"]["amount"]["min"] or 0
return amount if amount >= min_amount else 0.0
In plain words:
- DRY_RUN = True means the bot only writes down what it would do. Leave it on until you've watched the log for a long time.
- risk_money is the most one trade should lose — for example 1% of the account.
- per_coin is how much 1 BTC loses between entry and stop. If the stop isn't below the entry, the size is 0.
- amount_to_precision rounds to the decimals the exchange accepts.
- If the amount is below the exchange's minimum, it returns 0: skip the trade rather than round up.
Example: 10,000 USDT account, 1% risk = 100. Entry 60,000, stop 59,000: 100 ÷ 1,000 = 0.1 BTC. A math example, not a promise — fast moves can fill a stop worse than planned. Our position size calculator does the same math.
If you're using Leverage on crypto futures, know your liquidation price before anything else — see the crypto liquidation calculator. Many beginners skip leverage entirely while learning.
Step 3: Place orders — carefully
def buy(amount):
if DRY_RUN:
logging.info("DRY RUN buy %s %s", amount, SYMBOL)
return None
try:
order = exchange.create_order(SYMBOL, "market", "buy", amount)
logging.info("ORDER %s", order["id"])
return order
except ccxt.InsufficientFunds as e:
logging.error("Not enough funds: %s", e)
except ccxt.NetworkError as e:
logging.error("Network problem, check open orders before retrying: %s", e)
except ccxt.ExchangeError as e:
logging.error("Exchange said no: %s", e)
return None
In plain words:
- In dry-run mode it logs and stops there.
- create_order(symbol, type, side, amount) sends a market buy. A limit order would add a price.
- The except lines catch the 3 most common problems separately: not enough funds, the network dropping, or the exchange rejecting the order.
- The network case is the dangerous one. The order may have gone through even though the reply was lost. Before retrying, check fetch_open_orders and your balance — a blind retry can buy twice.
The stop: don't leave it in your head
A market buy on a spot exchange has no stop attached. You need one placed at the exchange, not just watched by your script — scripts crash and servers lose internet.
How stops work differs by exchange. Some support stop orders, some support take-profit and stop-loss together, some neither on spot. Check exchange.has in CCXT and the exchange's own docs, then test placing and cancelling a stop on the test network. If an exchange can't hold a stop for you, think hard before running a bot there.
The loop and the safety switches
Wrap it all in a loop that runs once per finished candle. Before each decision, check:
- The daily loss limit. Compare balance with the day's starting balance; after the limit (say 2%), no new trades until tomorrow. See daily loss limit.
- A kill switch you can flip without code — for example, the bot stops if a file named STOP exists.
- Open positions. Don't open a second trade by accident after a restart.
- A maximum order size, so a bug can't send a huge order.
- Logging of every decision, order and error.
Crypto never closes, so the bot can run while you sleep. That's exactly why these limits need to be in the code, not in your head. More: trading bot risk controls.
Test before you trust it
- Backtest the rule on past candles first, watching for look-ahead bias (using a candle before it's finished) and overfitting (tuning until the past looks perfect). See how to backtest in Python.
- Dry run on live prices for weeks, reading the log daily.
- Test network orders, including stops and cancels.
- Only then consider small real orders. Losses are possible at any size.
"Crypto bot" sellers are a classic trap. A bot promising steady daily returns is a red flag, not a feature. If you can't read the rules, you can't know how it loses. See AI trading bots explained.
Questions
What is CCXT?
A free, open-source library for Python and JavaScript that connects to many crypto exchanges with one set of commands, such as fetch_ohlcv for candles and create_order for orders.
Is CCXT safe to use?
The library itself is widely used and open source. The risk is in how you use it: protect your API keys, turn off withdrawal permission, start on a test network or dry run, and add stops and loss limits.
Does CCXT support paper trading?
CCXT can switch to an exchange's test network with set_sandbox_mode(True), but only if that exchange offers one. Otherwise, run your bot in a dry-run mode that logs orders instead of sending them.
Which exchanges work with CCXT?
About 100, including many large exchanges. Which ones you can legally use depends on where you live, so check the exchange's terms for your country.
Want the next step? We'll walk you through it.
Four free lessons by email, one every few days — each with a 10-minute exercise on a real chart.
Send Me the Free Lessons 4 short emails · No spam · Unsubscribe anytimeOr see the whole method, step by step:
See What’s Inside the Course Try everything free first. Decide when you're ready. · Open the free chart tool (works in the TradingView phone app)One chart idea a day, in plain English. Follow along: