目錄本文共 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,交易所規則可能調整,請以官方最新說明為準。
本文僅供學習參考,不構成投資建議。交易所的費率、活動與功能可能隨時調整,請以官方頁面為準。文中部分連結為推廣連結,詳見推廣披露。