目录本文共 6 节
交易所 API 的报错看起来五花八门,但绝大多数只有四类原因:本机时间不准、签名或 API Key 不对、请求太频繁、下单数量和价格不符合交易对规则。排查时先看错误码属于哪一类,再按「时间 → 密钥与权限 → 频率 → 订单参数」的顺序逐项检查,通常几分钟就能定位。下面的错误码含义全部核对过币安和欧易的官方 API 文档(截至 2026 年 10 月),代码在 CCXT 4.5.89 上实际运行过。
如果你还没有创建 API Key,先看 币安 API Key 创建与权限设置 和 欧易 API Key 创建教程;还没装 CCXT,先看 CCXT Python 入门。
币安和欧易常见 API 错误码速查表
先对照这张表判断你的报错属于哪一类。「CCXT 异常」一列是 CCXT 4.5.89 内置映射表里的实际类型,用 CCXT 时你捕获到的就是它。
| 交易所 | 错误码 | 官方提示(节选) | 含义 | CCXT 异常 |
|---|---|---|---|---|
| 币安 | -1021 | Timestamp for this request is outside of the recvWindow | 时间戳超出接收窗口 | InvalidNonce |
| 币安 | -1022 | Signature for this request is not valid | 签名无效 | AuthenticationError |
| 币安 | -2014 | API-key format invalid | Key 格式不对,多半复制错了 | AuthenticationError |
| 币安 | -2015 | Invalid API-key, IP, or permissions for action | Key、IP 白名单或权限不对 | AuthenticationError |
| 币安 | -1111 | Parameter has too much precision | 小数位数超过精度 | BadRequest |
| 币安 | -1013 | Filter failure: LOT_SIZE / NOTIONAL / PRICE_FILTER | 数量、金额或价格不符合过滤器 | BadRequest |
| 币安 | -2010 | Account has insufficient balance for requested action | 余额不足 | InsufficientFunds |
| 币安 | -1003 / HTTP 429 | Too much request weight used | 请求权重超限 | RateLimitExceeded |
| 币安 | HTTP 418 | 无 | 限频后仍继续请求,IP 被自动封禁 | DDoSProtection |
| 欧易 | 50102 | Timestamp request expired | 时间戳过期 | InvalidNonce |
| 欧易 | 50113 | Invalid signature | 签名无效 | AuthenticationError |
| 欧易 | 50111 | Invalid OK-ACCESS-KEY | Key 无效 | AuthenticationError |
| 欧易 | 50110 | Your IP is not included in your API key’s IP whitelist | IP 不在白名单 | PermissionDenied |
| 欧易 | 50101 | APIKey does not match current environment | 模拟盘 Key 连了实盘,或反过来 | AuthenticationError |
| 欧易 | 51008 | Order failed. Insufficient balance in account | 余额或保证金不足 | InsufficientFunds |
| 欧易 | 51020 / 51121 | 低于最小下单量 / 数量不是 lot size 的整数倍 | 下单数量不合规 | InvalidOrder |
| 欧易 | 50011 | Rate limit reached | 请求频率超限 | RateLimitExceeded |
两个容易踩的细节:
- 币安的精度错误在 CCXT 里是 BadRequest,不是 InvalidOrder。 如果你只写了
except ccxt.InvalidOrder,-1013 和 -1111 会漏网,建议把两者一起捕获。 - 欧易下单失败时外层 code 往往不是错误码本身。 下单接口返回的外层 code 可能是
"1",真正的原因在data数组里的sCode和sMsg,例如"sCode": "51008"。自己解析原始响应时别只看外层。
币安 -1021、欧易 50102:时间戳错误怎么解决?
签名请求必须带上发送时刻的时间戳,交易所用它防止请求被截获后重放。两家的规则不一样:
- 币安:时间戳不能比服务器时间快 1 秒以上,也不能落后超过
recvWindow。recvWindow不传时默认 5000 毫秒,最大 60000 毫秒,官方建议用 5000 或更小。CCXT 默认会替你带上recvWindow=10000。 - 欧易:请求头
OK-ACCESS-TIMESTAMP必须是 UTC 时间的 ISO 8601 格式并带毫秒,例如2020-12-08T09:08:57.715Z,和服务器时间相差超过 30 秒就会返回 50102。官方文档特别指出,本地时区偏移是 50102 最常见的原因。
第一步,先量一下你的电脑或服务器和交易所差多少:
import time
import ccxt
for exchange in [ccxt.binance(), ccxt.okx()]:
local_before = int(time.time() * 1000)
server = exchange.fetch_time() # 交易所服务器时间(毫秒)
local_after = int(time.time() * 1000)
local_mid = (local_before + local_after) // 2
print(f"{exchange.id:8s} 服务器时间 {exchange.iso8601(server)} "
f"本机偏差 {local_mid - server:+d} ms 往返耗时 {local_after - local_before} ms")
运行时的真实输出(2026 年 10 月 22 日):
binance 服务器时间 2026-10-22T09:30:14.791Z 本机偏差 -287 ms 往返耗时 285 ms
okx 服务器时间 2026-10-22T09:30:15.061Z 本机偏差 -270 ms 往返耗时 289 ms
偏差在几百毫秒以内都正常,这里的数值里还混着网络往返时间。如果偏差达到几秒甚至几分钟,问题就在本机:打开电脑的「自动设置时间」,云服务器检查 NTP 时间同步服务是否在运行。
第二步,在程序里让 CCXT 自动校准。开启 adjustForTimeDifference 后,load_markets() 会顺便查询一次服务器时间,后续签名都按校准后的时间生成:
binance = ccxt.binance({"options": {"adjustForTimeDifference": True}})
binance.load_markets()
print("timeDifference(ms) =", binance.options["timeDifference"])
timeDifference(ms) = -152
程序长时间运行时,时钟会慢慢漂移,可以每隔几小时调用一次 exchange.load_time_difference() 重新校准。不要靠把 recvWindow 调到 60000 来压住 -1021:窗口越大,请求在网络里被延迟或重放后依然有效的时间就越长。
币安 -2015、-1022,欧易 50111、50113:密钥和签名错误怎么排查?
这一类报错说明交易所不认你的身份。按下面的顺序检查,前一项没问题再看下一项:
- Key 本身:复制时有没有多带空格或换行?-2014 基本就是格式不对。确认 Key 没被删除或过期。
- 环境是否匹配:币安现货测试网和正式站的 Key 互不通用;欧易模拟盘 Key 只能配合模拟盘请求头使用,混用会报 50101。
- IP 白名单:绑定了白名单后,从别的 IP 发请求,币安报 -2015,欧易报 50110。家用宽带的公网 IP 会变,程序最好跑在固定 IP 的服务器上。
- 权限:读余额只要「读取」,下单还要「现货交易」。永远不要开提现权限。
- 签名本身(-1022 / 50113):用 CCXT 时签名由库生成,出错多半是 Secret 填错、欧易 Passphrase 填错。自己手写签名时,请对照 API 签名原理与官方示例复现 逐字节核对签名串。
用 CCXT 时,可以先在测试网上验证这套检查是否生效。下面这段代码按 CCXT 文档写法连接币安现货测试网,下一张远离市价的限价单,成功后立刻撤单,并把不同错误分门别类打印出来:
import os
import ccxt
exchange = ccxt.binance({
"apiKey": os.environ["BINANCE_API_KEY"],
"secret": os.environ["BINANCE_API_SECRET"],
"options": {"adjustForTimeDifference": True},
})
exchange.set_sandbox_mode(True) # 先连币安现货测试网,不动真钱
exchange.load_markets()
symbol = "BTC/USDT"
price = exchange.fetch_ticker(symbol)["last"] * 0.7 # 远离市价的限价,不会成交
amount = 0.0002
try:
order = exchange.create_order(symbol, "limit", "buy", amount, price)
print("下单成功", order["id"], order["status"])
exchange.cancel_order(order["id"], symbol) # 测试完立刻撤单
print("已撤单")
except ccxt.PermissionDenied as e:
print("权限或 IP 白名单问题:", e)
except ccxt.AuthenticationError as e:
print("Key、Secret 或签名有问题:", e)
except ccxt.InsufficientFunds as e:
print("余额不足:", e)
except (ccxt.InvalidOrder, ccxt.BadRequest) as e:
print("订单参数不符合交易规则:", e)
except ccxt.InvalidNonce as e:
print("时间戳超出接收窗口:", e)
except ccxt.RateLimitExceeded as e:
print("请求太频繁:", e)
为了让你看到报错长什么样,我们故意用两个不存在的 Key 跑了这段代码:第一次填一串格式明显不对的字符,第二次填一串 64 位的随机字符。测试网返回的真实结果分别是:
Key、Secret 或签名有问题: binance {"code":-2014,"msg":"API-key format invalid."}
Key、Secret 或签名有问题: binance {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
欧易模拟盘的写法类似,多一个 "password": os.environ["OKX_API_PASSPHRASE"],同样调用 set_sandbox_mode(True),CCXT 会自动加上 x-simulated-trading: 1 请求头。用假 Key 请求余额,返回的是 {"msg":"Invalid OK-ACCESS-KEY","code":"50111"}。
注意 except 的顺序:PermissionDenied 是 AuthenticationError 的子类,必须写在前面,否则永远走不到。
币安 -1003、429、418 和欧易 50011:频率限制怎么处理?
截至 2026 年 10 月,币安现货的限频按 IP 计算,不按 API Key。我们实际调用 exchangeInfo 读取到的限额是:
import ccxt
binance = ccxt.binance()
info = binance.publicGetExchangeInfo({"symbol": "BTCUSDT"})
for limit in info["rateLimits"]:
print(limit)
headers = binance.last_response_headers
print({k: v for k, v in headers.items() if k.lower().startswith("x-mbx-used-weight")})
{'rateLimitType': 'REQUEST_WEIGHT', 'interval': 'MINUTE', 'intervalNum': 1, 'limit': 6000}
{'rateLimitType': 'ORDERS', 'interval': 'SECOND', 'intervalNum': 10, 'limit': 100}
{'rateLimitType': 'ORDERS', 'interval': 'DAY', 'intervalNum': 1, 'limit': 200000}
{'rateLimitType': 'RAW_REQUESTS', 'interval': 'MINUTE', 'intervalNum': 5, 'limit': 300000}
{'x-mbx-used-weight': '131', 'x-mbx-used-weight-1m': '131'}
也就是每分钟 6000 请求权重、每 10 秒 100 笔订单、每天 20 万笔订单。每个接口的权重不同,响应头 X-MBX-USED-WEIGHT-1M 告诉你这一分钟已经用了多少。超限后返回 HTTP 429;收到 429 还继续请求,会被自动封 IP 并返回 418,封禁时长对屡犯者从 2 分钟逐步增加到 3 天。429 和 418 的响应头都带 Retry-After,单位是秒。
欧易的限频按接口分别规定,例如截至 2026 年 10 月,下单接口是每 2 秒 60 次(按用户和交易产品计),K 线接口是每 2 秒 40 次(按 IP 计)。超限返回 50011,账户级下单限速超限返回 50061。
处理原则:
- 保持 CCXT 的
enableRateLimit开启(默认就是开启),它会在请求之间自动留出间隔。 - 能用 WebSocket 推送就别轮询,行情和订单状态都有对应的推送频道。
- 收到限频错误要退避,等待时间逐次加倍,不要立刻重试。
下面是一个只对「可以重试」的错误重试的小工具,签名、权限、参数错误直接抛出,因为重试也不会好:
import time
import ccxt
def call_with_retry(fn, *args, retries=3, **kwargs):
"""只对“可以重试”的错误重试;签名、权限、参数错误直接抛出"""
for attempt in range(1, retries + 1):
try:
return fn(*args, **kwargs)
except ccxt.InvalidNonce:
# 时间戳问题:重新校准时间差后再试(-1021 / 50102)
fn.__self__.load_time_difference()
except (ccxt.RateLimitExceeded, ccxt.DDoSProtection):
# 频率限制:退避等待,等待时间逐次加倍
time.sleep(2 ** attempt)
except (ccxt.RequestTimeout, ccxt.ExchangeNotAvailable):
# 网络超时 / 交易所繁忙:稍等再试。注意下单超时要先查单,不要直接重下
time.sleep(1)
print(f"第 {attempt} 次失败,准备重试")
raise RuntimeError("重试次数用完")
binance = ccxt.binance({"options": {"adjustForTimeDifference": True}})
binance.load_markets()
ticker = call_with_retry(binance.fetch_ticker, "BTC/USDT")
print(ticker["symbol"], ticker["last"])
BTC/USDT 81770.02
(价格为运行时的行情。)
下单报 LOT_SIZE、NOTIONAL、51121:数量和价格精度怎么处理?
每个交易对都有自己的下单规则。币安把它们叫做过滤器:
- PRICE_FILTER:价格的最小变动单位(tickSize)。
- LOT_SIZE:数量的最小值、最大值和步长(stepSize)。
- NOTIONAL / MIN_NOTIONAL:价格乘以数量后的订单金额范围。
欧易对应的字段是 tickSz(价格步长)、lotSz(数量步长)和 minSz(最小下单量)。违反规则时,币安报 -1111(小数位太多)或 -1013 加具体的 Filter failure,欧易报 51121(不是 lot size 的整数倍)或 51020(低于最小下单量)。
CCXT 会把这些规则统一整理到 market["precision"] 和 market["limits"] 里。下面的函数先修正精度,再检查最小数量和最小金额:
import ccxt
def prepare_limit_order(exchange, symbol, amount, price):
"""下单前自检:按交易对规则修正精度,再检查最小数量和最小金额"""
market = exchange.market(symbol)
amount_str = exchange.amount_to_precision(symbol, amount) # 数量:按步长截断
price_str = exchange.price_to_precision(symbol, price) # 价格:按最小变动价位取整
cost = float(amount_str) * float(price_str)
limits = market["limits"]
problems = []
if limits["amount"]["min"] and float(amount_str) < limits["amount"]["min"]:
problems.append(f"数量小于最小下单量 {limits['amount']['min']}")
if limits["cost"]["min"] and cost < limits["cost"]["min"]:
problems.append(f"金额 {cost:.2f} 小于最小下单金额 {limits['cost']['min']}")
return amount_str, price_str, round(cost, 2), problems
symbol = "BTC/USDT"
for exchange in [ccxt.binance(), ccxt.okx()]:
exchange.load_markets()
market = exchange.market(symbol)
print(exchange.id, "precision =", market["precision"])
print(exchange.id, "limits.amount =", market["limits"]["amount"],
"limits.cost =", market["limits"]["cost"])
last = exchange.fetch_ticker(symbol)["last"]
far_price = last * 0.7 # 远离市价的限价,仅用于演示
print(" 原始输入 0.0123456789 BTC @", far_price)
print(" 修正结果", prepare_limit_order(exchange, symbol, 0.0123456789, far_price))
print(" 小额测试", prepare_limit_order(exchange, symbol, 0.00005, far_price))
运行时的真实输出(节选,价格为运行时行情的 70%):
binance precision = {'amount': 1e-05, 'price': 0.01, 'base': 1e-08, 'quote': 1e-08}
binance limits.amount = {'min': 1e-05, 'max': 9000.0} limits.cost = {'min': 5.0, 'max': 9000000.0}
原始输入 0.0123456789 BTC @ 57251.747
修正结果 ('0.01234', '57251.75', 706.49, [])
小额测试 ('0.00005', '57251.75', 2.86, ['金额 2.86 小于最小下单金额 5.0'])
okx precision = {'amount': 1e-08, 'price': 0.1}
okx limits.amount = {'min': 1e-05, 'max': 9999999999.0} limits.cost = {'min': None, 'max': 1000000.0}
原始输入 0.0123456789 BTC @ 57256.57
修正结果 ('0.01234567', '57256.6', 706.87, [])
小额测试 ('0.00005', '57256.6', 2.86, [])
从输出可以读出几件事:
- 同一个 BTC/USDT,两家规则不同。 截至 2026 年 10 月实测,币安数量步长 0.00001、价格步长 0.01、最小下单金额 5 USDT;欧易数量步长 0.00000001、价格步长 0.1、最小下单量 0.00001 BTC。代码里不要写死精度,每次启动都从
load_markets()读取。 - 数量是截断,价格是四舍五入。
amount_to_precision把 0.0123456789 截成 0.01234,不会向上凑,保证你不会因为进位而余额不足。 - 精度对了不代表能下单。 币安那笔 2.86 USDT 的小单精度完全合规,但低于 5 USDT 的最小金额,真发出去会收到
Filter failure: NOTIONAL。欧易这里没有给出最小金额,但仍受最小下单量约束。
CCXT 本身也会提前拦下一部分明显不合规的输入,不用发请求就能看到异常:
binance = ccxt.binance()
binance.load_markets()
try:
binance.market("ABCXYZ/USDT")
except ccxt.BadSymbol as e:
print("不存在的交易对 -> BadSymbol:", e)
try:
binance.amount_to_precision("BTC/USDT", 0.000001)
except ccxt.InvalidOrder as e:
print("数量小于步长 -> InvalidOrder:", e)
不存在的交易对 -> BadSymbol: binance does not have market symbol ABCXYZ/USDT
数量小于步长 -> InvalidOrder: binance amount of BTC/USDT must be greater than minimum amount precision of 0.00001
另外,CCXT 的 create_order 在发送前会自动调用 amount_to_precision 和 price_to_precision,所以用 CCXT 时 -1111 很少见,更常见的是最小金额和最小数量问题。自己手写请求时,数量和价格要用字符串传递,避免浮点数产生 0.30000000000000004 这样的多余小数位。
遇到 API 报错,按什么顺序排查最快?
把上面的内容压缩成一张排查流程表。拿到报错后从第一行往下对照:
| 顺序 | 检查项 | 典型错误 | 怎么确认 | 怎么修 |
|---|---|---|---|---|
| 1 | 网络和交易所状态 | 超时、HTTP 5xx、-1007、50001 | 公开接口(如 fetch_time)能否正常返回 |
稍后重试;下单类请求先查单 |
| 2 | 时间 | -1021、50102 | 对比本机时间和 fetch_time() |
开启自动对时和 adjustForTimeDifference |
| 3 | 环境 | -2015、50101 | Key 是在测试网或模拟盘创建,还是在正式站 | 测试 Key 配 set_sandbox_mode(True) |
| 4 | Key、IP 和权限 | -2014、-2015、50110、50111 | 在交易所 API 管理页面核对 | 重新复制 Key,补充白名单 IP,开对权限 |
| 5 | 签名 | -1022、50113 | 用官方示例参数复现签名 | 检查 Secret、Passphrase 和签名串拼接 |
| 6 | 频率 | -1003、429、418、50011 | 查看 X-MBX-USED-WEIGHT-1M 等响应头 |
退避等待,改用 WebSocket |
| 7 | 订单参数 | -1013、-1111、51020、51121 | 对照 market["limits"] 与 market["precision"] |
修正精度,提高订单金额 |
| 8 | 余额 | -2010、51008 | fetch_balance() 看可用余额,挂单会冻结资金 |
撤掉多余挂单或减少数量 |
写程序时,建议把每次报错的错误码、时间和请求参数(去掉签名和 Key)记到日志里。同一个错误反复出现时,回头看日志比临时猜测快得多。手续费也会影响「余额刚好够」的订单,费率细节见 交易手续费怎么算。所有测试请先在币安现货测试网或欧易模拟盘完成,确认无误后再用小金额连接正式账户。更多 API 与量化教程见 识途量化专栏。
常见问题
币安 API 报 -1021 Timestamp for this request is outside of the recvWindow 怎么办?
说明请求到达服务器时,时间戳和服务器时间相差超过了接收窗口。先打开系统自动对时,再在程序里用服务器时间校准,CCXT 可开启 adjustForTimeDifference。不建议把 recvWindow 调到很大来掩盖问题,币安官方建议 5000 毫秒或更小。
币安 API 报 -2015 Invalid API-key, IP, or permissions for action 是什么原因?
这个错误同时覆盖三种情况:Key 填错或已删除、当前服务器 IP 不在白名单内、Key 没开对应权限(例如没开现货交易就去下单)。还有一种常见情况是把测试网的 Key 拿去连正式环境,两边的 Key 不通用。
欧易 API 报 50113 Invalid signature 怎么排查?
欧易签名串是 时间戳 + 请求方法 + 请求路径 + 请求体,用 SecretKey 做 HMAC SHA256 后再 Base64。最常见的错误是请求路径漏了查询参数、POST 请求体和实际发送的 JSON 不一致、时间戳格式不是 UTC 毫秒 ISO 格式。用 CCXT 时多半是 Secret 或 Passphrase 填错。
下单报 Filter failure: LOT_SIZE 或 NOTIONAL 是什么意思?
LOT_SIZE 表示数量不符合该交易对的最小数量、最大数量或数量步长;NOTIONAL 表示价格乘以数量后的订单金额不在允许范围内,通常是金额太小。先用 load_markets 读取交易对规则,再用 amount_to_precision 修正数量并检查最小金额。
API 请求太频繁被封 IP 要多久才能恢复?
币安在你收到 429 后仍继续请求,会自动封禁 IP 并返回 418,封禁时长按重复次数递增,从 2 分钟到 3 天不等,响应头的 Retry-After 会告诉你还要等多少秒。欧易限频时返回 50011,按接口和账户分别计算,等待后降低请求速度即可。
准备好出发了吗?
参考来源
- Binance Developer Docs:Error codes for Binance(现货)
- Binance Developer Docs:General REST API Information(时间安全与频率限制)
- OKX API v5 文档:Error Code 与 REST 认证说明
- CCXT Manual:Error Handling 与 Precision
以上信息核实于 2026-10-22,交易所规则可能调整,请以官方最新说明为准。
本文仅供学习参考,不构成投资建议。交易所的费率、活动与功能可能随时调整,请以官方页面为准。文中部分链接为推广链接,详见推广披露。