バイナンス API のレート制限(限頻)における核心的なメカニズムは、ウェイトバケット(Weight Bucket) です。各 IP は1分あたり最大 6000 ウェイト(現物)を消費でき、各エンドポイントはその複雑さに応じて 1〜100 のウェイト分が差し引かれます。制限を超えると 429 Too Many Requests が返され、違反を繰り返すと 418 エラーにアップグレードされ、2分から最大3日間の IP 制限(BAN)が科されます。本記事では、「ウェイト計算 → レスポンスヘッダーの監視 → クライアント側での制限 → WebSocket による代替」の4つのステップに沿って完全なコードを提示し、リスク管理に抵触することなく安定して戦略を運用する方法を解説します。まだ API キーをお持ちでない方は、まず バイナンス公式サイト で本人確認(KYC)を完了させてください。アカウントがない方は 無料登録 から開設できます。
1. レート制限の3つの次元
バイナンスは、同一の IP または API キーに対して、3つの独立した制限を設けています。
| 次元 | 現物上限 | 先物上限 | 違反時のレスポンス |
|---|---|---|---|
| リクエストウェイト (REQUEST_WEIGHT) | 6000 / 分 | 2400 / 分 | 429 |
| 注文回数 (ORDERS) | 100 / 10秒; 200,000 / 日 | 300 / 10秒; 1200 / 分 | 429 |
| 接続数 (RAW_REQUESTS) | 61,000 / 5分 | 61,000 / 5分 | 429 |
| IP 制限 (BAN) | 連続した違反 | 連続した違反 | 418 |
重要な認識:注文用エンドポイントのウェイト自体は 1 ですが、同時に ORDERS バケットも消費します。いずれかのバケットが上限に达すると制限がかかります。
2. 現在のウェイトクォータを確認する
GET /api/v3/exchangeInfo の rateLimits フィールドを通じて、アカウントのリアルタイムなクォータ(配分)を取得できます。
curl -s "https://api.binance.com/api/v3/exchangeInfo" | \
jq '.rateLimits'
レスポンス例:
[
{"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": 61000}
]
VIP レベルの高いアカウントはウェイト上限の引き上げを申請できますが、標準アカウントの 6000 でも 90% 以上の戦略ニーズを満たすことができます。
3. 主要なエンドポイントのウェイト対照表
| エンドポイント | ウェイト | 説明 |
|---|---|---|
| GET /api/v3/ping | 1 | 疎通確認 |
| GET /api/v3/time | 1 | サーバー時刻 |
| GET /api/v3/exchangeInfo | 20 | 取引ルール(1時間キャッシュで十分) |
| GET /api/v3/ticker/price (単一通貨ペア) | 1 | 特定通貨ペアの価格 |
| GET /api/v3/ticker/price (全通貨ペア) | 4 | 全通貨ペアの価格を一括取得 |
| GET /api/v3/ticker/24hr (単一通貨ペア) | 1 | 特定通貨ペアの24h統計 |
| GET /api/v3/ticker/24hr (全通貨ペア) | 80 | 全通貨ペアの統計を一括取得 |
| GET /api/v3/depth limit=5/10/20/50/100 | 1 | 板情報 |
| GET /api/v3/depth limit=500 | 5 | 板情報 |
| GET /api/v3/depth limit=1000 | 10 | 板情報 |
| GET /api/v3/depth limit=5000 | 50 | 板情報(慎用) |
| GET /api/v3/klines | 2 | ローソク足 |
| GET /api/v3/historicalTrades | 5 | ヒストリカル取引 |
| GET /api/v3/account | 20 | アカウント残高 |
| GET /api/v3/openOrders (単一通貨ペア) | 6 | 現在のアクティブな注文 |
| GET /api/v3/openOrders (全通貨ペア) | 80 | すべてのアクティブな注文 |
| GET /api/v3/allOrders | 20 | 注文履歴 |
| POST /api/v3/order | 1 | 注文 |
| DELETE /api/v3/order | 1 | 注文キャンセル |
| DELETE /api/v3/openOrders | 1 | 全注文キャンセル |
パフォーマンスの落とし穴:単一通貨ペアの ticker/24hr をループで呼び出さないでください。パラメータなしの一括取得エンドポイントを使用すれば、300通貨ペア × 1 = 300 ウェイト消費するところを、わずか 80 ウェイトに削減できます。
4. レスポンスヘッダーを読み取り適応型レート制限を行う
すべての REST リクエストのレスポンスには現在の使用済みウェイトが含まれています。クライアント側でこれを読み取り、動的に調整すべきです。
import requests, time
BASE_URL = "https://api.binance.com"
class RateLimiter:
def __init__(self, max_weight=6000, safety_ratio=0.8):
self.max_weight = max_weight
self.safety = safety_ratio # コールドスタートの誤差を防ぐため 80% に設定
self.used_weight = 0
def update_from_headers(self, headers: dict):
used = headers.get("X-MBX-USED-WEIGHT-1m")
if used:
self.used_weight = int(used)
def should_wait(self) -> float:
"""推奨される待機秒数を返す。0 は即時呼び出し可能を意味する"""
threshold = self.max_weight * self.safety
if self.used_weight >= threshold:
# 次の分でリセットされるまでの推定秒数
return 60 - (int(time.time()) % 60)
return 0
limiter = RateLimiter()
def safe_get(path, params=None):
wait = limiter.should_wait()
if wait > 0:
print(f"[レート制限] {limiter.used_weight} ウェイト消費中、{wait}秒停止します")
time.sleep(wait)
r = requests.get(f"{BASE_URL}{path}", params=params, timeout=10)
limiter.update_from_headers(r.headers)
return r.json()
# 使用例
data = safe_get("/api/v3/ticker/24hr")
print(f"現在のウェイト消費状況: {limiter.used_weight}/6000")
5. トークンバケットによるレート制限(クライアント侧での制御)
レスポンスヘッダーを監視するよりも確実なのが、クライアント侧でのトークンバケット実装です。
import time, threading
class TokenBucket:
def __init__(self, capacity=6000, refill_per_sec=100):
self.capacity = capacity
self.tokens = capacity
self.refill = refill_per_sec # 6000 / 60秒 = 100トークン/秒
self.last = time.time()
self.lock = threading.Lock()
def acquire(self, cost=1):
with self.lock:
now = time.time()
elapsed = now - self.last
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill)
self.last = now
if self.tokens < cost:
wait = (cost - self.tokens) / self.refill
time.sleep(wait)
self.tokens = 0
else:
self.tokens -= cost
bucket = TokenBucket(capacity=6000, refill_per_sec=100)
def call(path, weight):
bucket.acquire(weight)
return requests.get(f"{BASE_URL}{path}").json()
# 注文(ウェイト 1)や残高確認(ウェイト 20)と組み合わせて使用
call("/api/v3/ticker/price", 1)
call("/api/v3/account", 20)
6. 429 および 418 エラーへの正しい対処法
1. 429 を受信した場合
def request_with_retry(method, url, **kwargs):
for attempt in range(3):
r = requests.request(method, url, **kwargs)
if r.status_code == 429:
retry_after = int(r.headers.get("Retry-After", 60))
print(f"レート制限に抵触、{retry_after}秒スリープします")
time.sleep(retry_after)
continue
if r.status_code == 418:
print("IP が制限されました。アプリケーションを停止する必要があります!")
raise SystemExit(1)
return r
raise Exception("最大试行回数を超えました")
Retry-After ヘッダーには 正確な待機秒数 が含まれているため、その時間分 sleep させます。
2. 418 を受信した場合
418 は重大な警告です。429 を受信した後にリクエストを送り続けると、IP BAN(最低2分、最长3日)が発生します。受信した場合は直ちにすべてのリクエストを停止し、少なくとも Retry-After に示された時間が経過するまで待機する必要があります。
7. WebSocket による代替:ウェイト消費をほぼゼロに
REST による相場取得は毎回ウェイトを消費しますが、WebSocket の購読は接続確立時に一度消費するだけで、リアルタイムのプッシュ配信はウェイトを占有しません。
import json, websocket
def on_message(ws, message):
data = json.loads(message)
print(f"{data['s']} 最新価格 {data['c']}, 24h出来高 {data['v']}")
ws = websocket.WebSocketApp(
"wss://stream.binance.com:9443/stream?streams=btcusdt@ticker/ethusdt@ticker",
on_message=on_message
)
ws.run_forever()
コスト換算:10通貨ペアのリアルタイム ticker を購読する場合、REST で1秒に1回ポーリングすると 60回/分 × ウェイト 1 = 600 ウェイト消費しますが、WebSocket 購読なら 0 ウェイト です。
8. ウェイト最適化の実践アドバイス
- exchangeInfo をキャッシュする:1時間に1回の更新で十分です。頻繁な呼び出しは 20ウェイト/回 の無駄遣いです。
- 一括取得を優先する:
/ticker/24hrでパラメータを指定せずに一括取得すれば、ループで個別に取得するよりウェイトを 90% 節約できます。 - 板情報は必要な limit だけ取得する:指値注文には limit=20(ウェイト 1)で十分です。5000層も取得する必要はありません。
- 注文ステータスの監視には userDataStream を使う:定期的にな
GET /orderよりも効率的で、ウェイト消費もゼロです。 - 全注文キャンセルには一括エンドポイントを使う:
DELETE /openOrders?symbol=BTCUSDTはウェイト 1 で、個別にキャンセルするより効率的です。 - 時間帯による制限:UTC 0時、8時、16時は決済ウィンドウであり、ウェイト消耗が激しくなります。戦略的にピーク時間を避けることを検討してください。
9. よくある質問 FAQ
Q1: ウェイトは IP ごとに計算されますか、それとも API キーごとに計算されますか?
A: 主に IP ごとです。同一 IP 下の複数のキーはウェイトバケットを共有します。IP を切り替えることで IP レベルの制限は回避できますが、注文回数制限は引き続きアカウントごとに計算されます。プロキシで動的に IP を切り替える行為は検知されるリスクがあるため推奨されません。
Q2: レスポンスヘッダーの X-MBX-USED-WEIGHT 和 X-MBX-USED-WEIGHT-1m 有什么区别?
A: X-MBX-USED-WEIGHT-1m が公式の推奨で、1分間のウィンドウであることを明示しています。X-MBX-USED-WEIGHT は歴史的な経緯で残っているフィールドで、値は同じです。-1m サフィックスの方を優先してください。
Q3: 418 で BAN された場合、どのくらい待つ必要がありますか?
A: 初回は 2分ですが、繰り返すと 5/15/60分と延び、最悪の場合は 3日間になります。Retry-After ヘッダーの指示に従ってください。復帰後は直ちにコードを最適化し、短期間での再違反を避けてください。
Q4: マルチスレッドで並列呼び出しを行う場合、ウェイトをどう計算すべきですか?
A: プロセスレベルで共通のトークンバケットを使用するか(第5セクション参照)、複数マシンで実行する場合は Redis による分散トークンバケットが必要です。単一マシン内であれば threading.Lock で十分です。
Q5: テストネット (testnet.binance.vision) のウェイトルールは同じですか?
A: テストネットのウェイト制限は通常より緩やか(主ネットの10倍程度)ですが、ルール構造は同一です。テストネットでの消費量で本番を判断せず、リリース前に必ず主ネットで小規模な検証を行ってください。
制限戦略を確認したら、カテゴリナビ に戻り、「API連携」カテゴリから WebSocket や署名の実践ガイドをご覧ください。