目录本文共 8 节
CCXT 是一个开源的加密货币交易所接口库,用 pip install ccxt 安装后,同一套 Python 代码就能连接币安和欧易:fetch_ticker 查行情、fetch_ohlcv 取 K 线、load_markets 读交易对精度,这些公开接口不需要 API Key;传入 Key 后再用 fetch_balance 查余额、create_limit_buy_order 挂单、cancel_order 撤单。本文所有公开接口代码都在 CCXT 4.5.89、Python 3.13 下实际运行过,私有接口请先在币安现货测试网或欧易模拟盘运行。
CCXT 是什么?为什么用它连接币安和欧易?
每家交易所的 API 写法都不一样:币安的交易对叫 BTCUSDT,签名放在查询参数里;欧易叫 BTC-USDT,签名放在请求头里,还要多一个 Passphrase。CCXT 把这些差异封装起来,对外提供统一的方法名和统一的数据格式:
- 交易对统一写成
BTC/USDT,CCXT 自动换成各交易所的内部代码。 - 签名、时间戳、请求头都由 CCXT 处理,你只需要提供 Key。
- 错误统一成
AuthenticationError、InsufficientFunds、InvalidOrder等异常类型。
CCXT 支持上百家交易所,本文只讲币安和欧易。同一个安装包里有三种用法:import ccxt 是同步写法,最适合入门和写小脚本,本文全部使用这种;import ccxt.async_support 是异步写法,适合同时请求很多交易对;import ccxt.pro 提供 WebSocket 实时推送,例如 watch_ticker,适合需要持续接收行情的程序。三者的方法名和返回格式基本一致,学会同步版本后迁移很容易。
要注意的是,CCXT 只是帮你「发请求」的工具,它不保管你的资产,也不会替你做任何交易决定。Key 存在你自己的电脑上,请求从你的电脑直接发往交易所,中间不经过第三方服务器。要了解底层签名是怎么算的,可以看 交易所 API 签名原理。
怎么安装 CCXT?
建议为每个项目建一个虚拟环境,避免和电脑自带的 Python 环境互相干扰:
python3 -m venv .venv
source .venv/bin/activate # Windows 用 .venv\Scripts\activate
pip install ccxt
python -c "import ccxt; print(ccxt.__version__)"
我们运行时输出的版本号是 4.5.89。CCXT 更新很频繁,方法名基本稳定,但个别交易所的默认参数可能变化,遇到和本文不一致的行为,先看看自己的版本号。
不用 API Key 怎么查行情、K 线和交易对规则?
下面这个脚本对币安和欧易依次查询服务器时间、BTC/USDT 最新行情、最近 3 根 1 小时 K 线和交易对规则,全部是公开接口:
import ccxt
print("CCXT 版本:", ccxt.__version__)
binance = ccxt.binance({
"enableRateLimit": True,
"options": {"fetchMarkets": {"types": ["spot"]}}, # 只加载现货市场,启动更快
})
okx = ccxt.okx({"enableRateLimit": True})
SYMBOL = "BTC/USDT"
for ex in (binance, okx):
print(f"\n===== {ex.id} =====")
# 1. 服务器时间:签名请求前先确认本机时间没有偏差
server_ms = ex.fetch_time()
print("服务器时间:", ex.iso8601(server_ms), "本机偏差(ms):", ex.milliseconds() - server_ms)
# 2. 最新行情
t = ex.fetch_ticker(SYMBOL)
print("最新价:", t["last"], "买一:", t["bid"], "卖一:", t["ask"],
"24h 涨跌%:", round(t["percentage"], 2), "24h 成交量:", t["baseVolume"])
# 3. 最近 3 根 1 小时 K 线:[时间, 开, 高, 低, 收, 成交量]
for ts, o, h, l, c, v in ex.fetch_ohlcv(SYMBOL, timeframe="1h", limit=3):
print(ex.iso8601(ts), o, h, l, c, round(v, 4))
# 4. 交易对规则:精度与最小下单量
markets = ex.load_markets()
m = ex.market(SYMBOL)
print("市场数量:", len(markets), "交易所内部代码:", m["id"])
print("价格步长:", m["precision"]["price"], "数量步长:", m["precision"]["amount"])
print("最小数量:", m["limits"]["amount"]["min"], "最小金额:", m["limits"]["cost"]["min"])
print("数量 0.123456789 规整后:", ex.amount_to_precision(SYMBOL, 0.123456789))
print("价格 65432.123456 规整后:", ex.price_to_precision(SYMBOL, 65432.123456))
2026 年 10 月 22 日运行时的真实输出(价格为运行时数据,仅作格式示例):
CCXT 版本: 4.5.89
===== binance =====
服务器时间: 2026-10-22T09:33:27.310Z 本机偏差(ms): -124
最新价: 81838.01 买一: 81838.0 卖一: 81838.01 24h 涨跌%: -2.11 24h 成交量: 21704.10475
2026-10-22T07:00:00.000Z 82491.56 82531.17 82111.0 82239.46 1264.4365
2026-10-22T08:00:00.000Z 82239.46 82267.79 81844.01 81861.24 1017.8519
2026-10-22T09:00:00.000Z 81861.25 81933.02 81653.0 81838.0 477.5245
市场数量: 3736 交易所内部代码: BTCUSDT
价格步长: 0.01 数量步长: 1e-05
最小数量: 1e-05 最小金额: 5.0
数量 0.123456789 规整后: 0.12345
价格 65432.123456 规整后: 65432.12
===== okx =====
服务器时间: 2026-10-22T09:33:28.170Z 本机偏差(ms): -119
最新价: 81834.5 买一: 81834.4 卖一: 81834.5 24h 涨跌%: -2.13 24h 成交量: 7411.60789259
2026-10-22T07:00:00.000Z 82500.1 82543.3 82131.4 82249.9 669.1373
2026-10-22T08:00:00.000Z 82249.9 82278.4 81850.0 81861.2 445.3618
2026-10-22T09:00:00.000Z 81861.2 81940.0 81638.8 81834.5 214.3707
市场数量: 4578 交易所内部代码: BTC-USDT
价格步长: 0.1 数量步长: 1e-08
最小数量: 1e-05 最小金额: None
数量 0.123456789 规整后: 0.12345678
价格 65432.123456 规整后: 65432.1
fetch_ticker 和 fetch_ohlcv 返回了什么?
fetch_ticker 返回一个字典,常用字段有 last(最新成交价)、bid/ask(买一、卖一)、high/low(24 小时最高最低)、baseVolume(24 小时成交量,单位是 BTC)、percentage(24 小时涨跌幅)。fetch_ohlcv 返回列表,每一项依次是 K 线开盘时间(毫秒时间戳)、开、高、低、收、成交量,最后一根是还没走完的当前 K 线。时间周期写 1m、5m、1h、1d 等,limit 控制根数。
本机偏差那一行也值得留意:签名请求带时间戳,币安默认只接受 5 秒内的请求,欧易超过 30 秒会拒绝。偏差在几百毫秒内很正常,如果差了好几秒,先开启电脑的自动对时。
fetch_order_book:怎么看买卖盘深度?
想知道当前价位挂了多少单,用 fetch_order_book。下面的代码取买卖盘前 3 档,并顺便演示交易对写错时的异常:
import ccxt
binance = ccxt.binance({"options": {"fetchMarkets": {"types": ["spot"]}}})
okx = ccxt.okx()
for ex in (binance, okx):
ob = ex.fetch_order_book("BTC/USDT", limit=5)
asks = [row[:2] for row in ob["asks"][:3]] # 每档只取 [价格, 数量]
bids = [row[:2] for row in ob["bids"][:3]]
print(ex.id, "卖盘:", asks)
print(ex.id, "买盘:", bids)
print(ex.id, "买卖价差:", round(asks[0][0] - bids[0][0], 2))
try:
binance.fetch_ticker("BTC/USDC.X") # 故意写一个不存在的交易对
except ccxt.BadSymbol as e:
print("BadSymbol:", e)
运行时输出:
binance 卖盘: [[81791.5, 0.13627], [81791.51, 0.00042], [81791.69, 7e-05]]
binance 买盘: [[81791.49, 11.21318], [81791.25, 0.04184], [81791.24, 2.06615]]
binance 买卖价差: 0.01
okx 卖盘: [[81796.8, 1.38093972], [81797.7, 0.21551712], [81798.0, 1.02e-05]]
okx 买盘: [[81796.7, 1.0170818], [81796.6, 0.04428254], [81796.3, 0.15659987]]
okx 买卖价差: 0.1
BadSymbol: binance does not have market symbol BTC/USDC.X
卖盘按价格从低到高排,买盘从高到低排,第一档就是卖一和买一。BTC/USDT 这种主流交易对的价差只有一个价格步长;小币种的价差和每档数量可能差很多,市价单滑点也会更大。欧易返回的每档数据比币安多一个字段,所以代码里用 row[:2] 只取价格和数量,两家的处理方式就一致了。
为什么下单前一定要看精度和最小下单量?
交易所对每个交易对都规定了价格步长、数量步长和最小下单量,不符合就会被拒单。从上面的输出整理成表:
| 规则 | 币安 BTC/USDT | 欧易 BTC/USDT |
|---|---|---|
| 价格步长 | 0.01 | 0.1 |
| 数量步长 | 0.00001 BTC | 0.00000001 BTC |
| 最小数量 | 0.00001 BTC | 0.00001 BTC |
| 最小金额 | 5 USDT | 接口未返回(以数量限制为准) |
另外,币安现货还有 PERCENT_PRICE_BY_SIDE 规则:运行时 BTCUSDT 的买单价格不能低于近 5 分钟均价的 50%,否则报价格过滤失败。所以测试挂单时,价格「远离市价」也要有个度。
amount_to_precision 和 price_to_precision 会按步长规整数字,数量是向下截断而不是四舍五入:0.123456789 在币安变成 0.12345。截断后金额可能掉到最小金额以下,计算数量时要留一点余量。各种订单类型的区别见 限价单、市价单与止盈止损。
私有接口要准备什么?
查余额、下单、撤单需要 API Key。申请方法和权限设置分别见 币安 API Key 申请教程 和 欧易 API 创建教程,核心原则只有三条:只开读取和现货交易、不开提现、绑定 IP。
Key 不要写进代码,用环境变量传入:
# macOS / Linux:只在当前终端窗口有效
export BINANCE_API_KEY="你的币安测试网 API Key"
export BINANCE_API_SECRET="你的币安测试网 Secret Key"
export OKX_API_KEY="你的欧易模拟盘 API Key"
export OKX_API_SECRET="你的欧易模拟盘 Secret Key"
export OKX_API_PASSPHRASE="你的欧易模拟盘 Passphrase"
Windows PowerShell 写成 $env:BINANCE_API_KEY="..."。不要把带 Key 的命令保存进脚本文件再上传到代码仓库。
环境变量只在当前终端窗口有效,关掉就没了。长期使用时,可以把这几行放进一个只有你自己能读的本地配置文件,在运行前加载;如果项目用 Git 管理,记得把这个文件加进 .gitignore。私有接口的权限也要对得上:只查余额,读取权限就够;要跑下面的挂单和撤单脚本,测试网 Key 需要同时有读取和现货交易权限。
CCXT 怎么切换到币安测试网和欧易模拟盘?
截至 CCXT 4.5.89,两家交易所的切换方式如下(均已实测):
| 写法 | 效果 |
|---|---|
币安 set_sandbox_mode(True) |
请求地址换成 testnet.binance.vision,只能用现货 /api 接口 |
币安 enable_demo_trading(True) |
请求地址换成 demo-api.binance.com,对应币安网页版模拟交易 |
欧易 set_sandbox_mode(True) |
域名不变,所有请求自动加上 x-simulated-trading: 1 |
币安的两种模式不能同时打开,实测先开 sandbox 再开 demo 会抛出 NotSupported。另外,欧易模拟盘有独立的订单簿,价格和实盘不同,不要用模拟盘行情做判断。
完整脚本:查余额、挂单、查挂单、撤单
下面的脚本流程是:查 USDT 余额 → 按市价的 70% 计算一个不会成交的限价买单 → 下单 → 查当前挂单 → 立即撤单。保存为 trade_demo.py,运行 python trade_demo.py binance 或 python trade_demo.py okx:
import os
import sys
import time
import ccxt
USE_TESTNET = True # 先在币安现货测试网 / 欧易模拟盘跑通,再考虑改成 False
SYMBOL = "BTC/USDT"
def make_exchange(name):
if name == "binance":
ex = ccxt.binance({
"apiKey": os.environ["BINANCE_API_KEY"],
"secret": os.environ["BINANCE_API_SECRET"],
"enableRateLimit": True,
"options": {"defaultType": "spot", "fetchMarkets": {"types": ["spot"]}},
})
elif name == "okx":
ex = ccxt.okx({
"apiKey": os.environ["OKX_API_KEY"],
"secret": os.environ["OKX_API_SECRET"],
"password": os.environ["OKX_API_PASSPHRASE"], # 欧易的 Passphrase 填在 password
"enableRateLimit": True,
})
else:
raise ValueError("只支持 binance 或 okx")
if USE_TESTNET:
ex.set_sandbox_mode(True) # 币安→testnet.binance.vision;欧易→加请求头 x-simulated-trading: 1
return ex
RETRYABLE = (ccxt.RequestTimeout, ccxt.ExchangeNotAvailable, ccxt.RateLimitExceeded, ccxt.DDoSProtection)
def with_retry(fn, *args, retries=3, **kwargs):
"""只用于查询类请求:网络抖动或限频时等待后重试。"""
for i in range(retries):
try:
return fn(*args, **kwargs)
except RETRYABLE as e:
wait = 2 ** i
print(f"{type(e).__name__},{wait} 秒后重试……")
time.sleep(wait)
return fn(*args, **kwargs)
def main(name):
ex = make_exchange(name)
with_retry(ex.load_markets)
market = ex.market(SYMBOL)
# 1. 查余额
balance = with_retry(ex.fetch_balance)
usdt = balance.get("USDT", {})
print("USDT 可用:", usdt.get("free"), "冻结:", usdt.get("used"))
# 2. 算一个远低于市价、不会成交的限价买单
last = with_retry(ex.fetch_ticker, SYMBOL)["last"]
price = float(ex.price_to_precision(SYMBOL, last * 0.7))
min_cost = market["limits"]["cost"]["min"] or 5
amount = max(market["limits"]["amount"]["min"], min_cost * 1.2 / price)
amount = float(ex.amount_to_precision(SYMBOL, amount))
print(f"准备挂单:买入 {amount} BTC,价格 {price}(市价约 {last})")
# 3. 下单:不自动重试,避免网络超时后重复下单
order = ex.create_limit_buy_order(SYMBOL, amount, price)
print("下单成功,订单号:", order["id"], "状态:", order["status"])
try:
# 4. 查当前挂单
open_orders = with_retry(ex.fetch_open_orders, SYMBOL)
print("当前挂单:", [(o["id"], o["price"], o["amount"]) for o in open_orders])
finally:
# 5. 无论如何都撤掉刚才的订单
ex.cancel_order(order["id"], SYMBOL)
print("已撤单:", order["id"])
if __name__ == "__main__":
name = sys.argv[1] if len(sys.argv) > 1 else "binance"
try:
main(name)
except KeyError as e:
print("缺少环境变量:", e)
except ccxt.AuthenticationError as e:
print("认证失败:检查 Key/Secret/Passphrase、IP 白名单、权限,以及是不是用了实盘 Key 连测试环境。", e)
except ccxt.InsufficientFunds as e:
print("余额不足:", e)
except ccxt.InvalidOrder as e:
print("订单参数不符合交易对规则(价格、数量、最小金额):", e)
except ccxt.NetworkError as e:
print("网络问题,稍后再试:", e)
except ccxt.ExchangeError as e:
print("交易所返回错误:", e)
这个脚本我们测到了哪一步?
查余额、下单这些私有接口需要你自己的测试网 Key。下面是 2026 年 10 月用这段代码实际运行的结果:
- 没设环境变量时输出
缺少环境变量: 'BINANCE_API_KEY'。 - 用假 Key 连币安测试网,异常被正确捕获:
认证失败:…… binance {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}。 - 用假 Key 连欧易模拟盘:
认证失败:…… okx {"msg":"Invalid OK-ACCESS-KEY","code":"50111"}。 - 挂单参数计算(拦截请求、不实际发送):币安算出价格 57281.01、数量 0.0001、金额约 5.73 USDT,满足 5 USDT 最小金额;欧易算出价格 57849.3、数量 0.00010371。CCXT 实际发往币安测试网的是
POST /api/v3/order,发往欧易的是POST /api/v5/trade/batch-orders,订单模式为现货cash。
换成你自己的测试网 Key 后,正常会依次打印余额、挂单参数、订单号、当前挂单列表和「已撤单」。
两个细节:一是 CCXT 默认会在订单的自定义订单号里加上自己的标识前缀(币安是 x- 开头的 newClientOrderId,欧易是 clOrdId 和 tag),想用自己的订单号,可以在下单时通过 params 传入 newClientOrderId 或 clOrdId;二是 CCXT 对币安默认使用 10000 毫秒的 recvWindow,比币安文档默认的 5000 毫秒宽松。
新手用 CCXT 最常遇到哪些问题?
| 现象 | 原因 | 解决办法 |
|---|---|---|
BadSymbol: ... does not have market symbol |
交易对写错,或该交易所没有这个交易对 | 用 BTC/USDT 这种统一写法,先 load_markets() 再查 ex.symbols |
币安 load_markets 很慢 |
默认同时加载现货、U 本位和币本位合约市场 | 实测默认加载 4697 个市场用时约 1.6 秒,只加载现货是 3736 个、约 0.5 秒 |
欧易提示缺少 password |
没传 Passphrase | 在 password 参数里填 Passphrase |
测试网能查行情,查余额报 -2015 |
用了实盘 Key,或测试网 Key 已随重置失效 | 到 testnet.binance.vision 重新生成 Key |
订单状态一直是 open |
限价单价格没到,正常挂单中 | 用 fetch_order 查询,或 cancel_order 撤单 |
订单的 status 字段在 CCXT 里统一为几种取值:open 表示还在挂单,closed 表示全部成交,canceled 表示已撤单。部分成交的订单仍然是 open,可以看 filled(已成交数量)和 remaining(剩余数量)两个字段判断进度。
CCXT 异常怎么处理?哪些错误可以重试?
CCXT 把各交易所的错误码映射成统一的异常类,大致分两类:
| 异常 | 含义 | 能否重试 |
|---|---|---|
RequestTimeout、ExchangeNotAvailable |
超时、交易所暂时不可用 | 查询可以,下单要先确认 |
RateLimitExceeded、DDoSProtection |
请求太快 | 等待后重试 |
InvalidNonce |
时间戳过期,例如欧易 50102 |
对时后重试 |
AuthenticationError |
Key、签名、Passphrase、IP 有问题,例如币安 -2015、欧易 50113 |
不要重试,先修配置 |
InsufficientFunds |
余额不足,例如欧易 51008 |
不要重试 |
InvalidOrder |
订单参数违规,例如币安 -2010、欧易 51020 |
不要重试,改参数 |
前三类在 CCXT 里都属于 NetworkError,后三类属于 ExchangeError。下单请求超时并不代表没下成功:请求可能已经到达交易所,只是响应没回来。如果这时盲目重发,可能挂出两笔一样的订单。稳妥的做法是:下单时传入自己生成的订单号,超时后先用 fetch_open_orders 查一下这笔订单在不在,再决定是否补单。
常见问题
CCXT 连接欧易为什么提示缺少 password?
欧易的 API 除了 API Key 和 Secret Key,还需要创建 Key 时你自己设置的 Passphrase。在 CCXT 里 Passphrase 填在 password 参数中,不传或填错就无法调用余额、下单等私有接口,欧易会返回 50104 或 50105。
CCXT 查行情需要 API Key 吗?
不需要。fetch_ticker、fetch_ohlcv、fetch_order_book、load_markets 这些行情类方法调用的都是交易所公开接口,直接创建 ccxt.binance() 或 ccxt.okx() 实例就能用。只有查余额、下单、撤单等私有接口才需要 Key。
CCXT 下单报 InvalidOrder 怎么办?
InvalidOrder 通常是订单参数不符合交易对规则,例如价格步长不对、数量低于最小值、下单金额低于最小金额,或限价偏离市价太多。先用 load_markets 读出精度和限制,再用 amount_to_precision 和 price_to_precision 规整。
CCXT 支持币安测试网和欧易模拟盘吗?
支持。截至 CCXT 4.5.89,对币安调用 set_sandbox_mode(True) 会切到现货测试网,enable_demo_trading(True) 会切到币安模拟交易;对欧易调用 set_sandbox_mode(True) 会自动加上 x-simulated-trading: 1 请求头。三者都要配合对应环境的专用 Key。
CCXT 的 enableRateLimit 有什么用?
开启后,CCXT 会按交易所的限频规则在请求之间自动等待,避免请求过快被拒绝或封禁 IP。新版 CCXT 默认就是开启状态,代码里写上可以让意图更清楚。
准备好出发了吗?
参考来源
- CCXT 官方文档(Manual)
- Binance Spot API 文档:Filters(PRICE_FILTER、LOT_SIZE、NOTIONAL)
- Binance Spot Test Network
- OKX API v5 文档:Demo Trading Services
以上信息核实于 2026-10-22,交易所规则可能调整,请以官方最新说明为准。
本文仅供学习参考,不构成投资建议。交易所的费率、活动与功能可能随时调整,请以官方页面为准。文中部分链接为推广链接,详见推广披露。