バイナンスの WebSocket リアルタイム相場を取得する最適な方法は、wss://stream.binance.com:9443/stream?streams= という混合ストリーム(Combined Streams)エンドポイントを使用することです。1つの接続で最大 1024 個のストリーム を購読でき、メッセージの遅延は 50ms 未満、ウェイト(Weight)の消費はほぼゼロです。本記事では、単一ストリーム、混合ストリーム、動的サブスクリプション、ローカルでの板情報のマッチング、再接続処理を含む完全な Python および Node.js コードを紹介します。すべてそのまま実行可能です。開始する前に、バイナンスのアカウントをまだお持ちでない方は、バイナンス公式サイト で公式入口を確認するか、無料登録 を完了させてください。
一、WebSocket エンドポイント一覧
| カテゴリ | URL | 用途 |
|---|---|---|
| 現物(Spot)単一ストリーム | wss://stream.binance.com:9443/ws/{stream} | 1つのストリームのみ購読 |
| 現物(Spot)混合ストリーム | wss://stream.binance.com:9443/stream?streams={s1/s2/...} | 複数購読、メッセージにストリーム名を含む |
| 現物(Spot)予備 | wss://data-stream.binance.vision/ws/{stream} | 公式バックアップ回線 |
| 先物(Futures)U本位 | wss://fstream.binance.com/stream?streams= | 無期限先物 |
| 先物(Futures)コイン本位 | wss://dstream.binance.com/stream?streams= | コイン本位先物 |
| テストネット | wss://stream.testnet.binance.vision/ws | テストネット |
接続制限:単一 IP あたり最大 300 接続、1接続あたり最大 1024 ストリーム。24 時間ごとに自動切断されるため、クライアント側で再接続処理を実装する必要があります。
二、ストリーム(Stream)タイプ一覧
バイナンスは主に 11 種類の相場ストリームを提供しています。
| Stream | 形式 | 送信頻度 | 説明 |
|---|---|---|---|
| aggTrade | {symbol}@aggTrade |
リアルタイム | 集約約定 |
| trade | {symbol}@trade |
リアルタイム | 逐次約定 |
| kline_1m | {symbol}@kline_1m |
毎秒 | K線(1m/5m/1h/...) |
| miniTicker | {symbol}@miniTicker |
1000ms | 簡易ティッカー |
| ticker | {symbol}@ticker |
1000ms | 24h 完全統計 |
| bookTicker | {symbol}@bookTicker |
リアルタイム | 最良気配値(Best Bid/Ask) |
| depth | {symbol}@depth |
1000ms | 板情報(20/50/100 段階) |
| depth@100ms | {symbol}@depth@100ms |
100ms | 板情報の差分(増量更新) |
| !miniTicker@arr | !miniTicker@arr |
1000ms | 全市場簡易ティッカー |
| !ticker@arr | !ticker@arr |
1000ms | 全市場完全統計 |
| !bookTicker | !bookTicker |
リアルタイム | 全市場最良気配値 |
命名規則:小文字を使用し、@ 記号で区切ります(例:BTCUSDT@ticker ではなく btcusdt@ticker)。
三、Python asyncio による単一ストリーム購読
import asyncio, json
import websockets
async def subscribe_ticker(symbol: str):
url = f"wss://stream.binance.com:9443/ws/{symbol.lower()}@ticker"
async with websockets.connect(url, ping_interval=180) as ws:
async for message in ws:
data = json.loads(message)
print(f"{data['s']}: 最新価格 {data['c']}, "
f"24h 変動率 {data['P']}%, "
f"出来高 {data['v']}")
asyncio.run(subscribe_ticker("BTCUSDT"))
出力例:
BTCUSDT: 最新価格 68420.12, 24h 変動率 2.35%, 出来高 45231.2
BTCUSDT: 最新価格 68421.00, 24h 変動率 2.36%, 出来高 45231.8
四、複数通貨ペアを混合ストリームで購読
1つの接続で 10 通貨ペアのティッカーと 5 通貨ペアの K 線を購読する例:
import asyncio, json
import websockets
STREAMS = [
"btcusdt@ticker", "ethusdt@ticker", "bnbusdt@ticker",
"solusdt@ticker", "xrpusdt@ticker", "adausdt@ticker",
"dogeusdt@ticker", "maticusdt@ticker", "dotusdt@ticker",
"avaxusdt@ticker",
"btcusdt@kline_1m", "ethusdt@kline_1m",
"bnbusdt@kline_1m", "solusdt@kline_1m", "xrpusdt@kline_1m"
]
async def combined_stream():
streams = "/".join(STREAMS)
url = f"wss://stream.binance.com:9443/stream?streams={streams}"
async with websockets.connect(url, ping_interval=180) as ws:
async for message in ws:
payload = json.loads(message)
stream = payload["stream"]
data = payload["data"]
if "@ticker" in stream:
print(f"[{stream}] {data['s']}: {data['c']}")
elif "@kline" in stream:
k = data["k"]
if k["x"]: # K線が確定した時のみ出力
print(f"[{stream}] {k['s']} {k['i']} "
f"O:{k['o']} H:{k['h']} L:{k['l']} C:{k['c']}")
asyncio.run(combined_stream())
五、動的な購読/解除(SUBSCRIBE メソッド)
接続確立後、JSON メッセージを送信して動的にサブスクリプションを追加または削除できます。
import asyncio, json
import websockets
async def dynamic_sub():
url = "wss://stream.binance.com:9443/ws"
async with websockets.connect(url) as ws:
# BTC と ETH の bookTicker を購読
await ws.send(json.dumps({
"method": "SUBSCRIBE",
"params": ["btcusdt@bookTicker", "ethusdt@bookTicker"],
"id": 1
}))
count = 0
async for msg in ws:
data = json.loads(msg)
if "result" in data:
print(f"購読確認: {data}")
continue
count += 1
print(f"{data['s']} 買一 {data['b']} 売一 {data['a']}")
# 50件受信後に ETH の購読を解除し、BTC のみ残す
if count == 50:
await ws.send(json.dumps({
"method": "UNSUBSCRIBE",
"params": ["ethusdt@bookTicker"],
"id": 2
}))
print("ETH の購読を解除しました")
asyncio.run(dynamic_sub())
六、板情報の増量マージ(ローカルでのオーダーブック維持)
高頻度トレード戦略では、ローカルでオーダーブックを維持する必要があります。手順は以下の通りです:
@depth@100msの差分ストリームを購読し、バッファに保存する。- REST API の
GET /api/v3/depth?symbol=X&limit=1000でスナップショットを取得する。 - バッファ内の
u < lastUpdateIdのメッセージを破棄する。 U <= lastUpdateId+1 <= uを満たすメッセージから順に適用する。- 以降のメッセージを適用し続ける。この際、
puが直前のメッセージのuと一致することを確認する。
import asyncio, json, aiohttp
import websockets
from sortedcontainers import SortedDict
class OrderBook:
def __init__(self, symbol):
self.symbol = symbol.lower()
self.bids = SortedDict() # 価格 -> 数量
self.asks = SortedDict()
self.last_update_id = 0
self.buffer = []
def apply_update(self, bids, asks):
for price, qty in bids:
price, qty = float(price), float(qty)
if qty == 0:
self.bids.pop(price, None)
else:
self.bids[price] = qty
for price, qty in asks:
price, qty = float(price), float(qty)
if qty == 0:
self.asks.pop(price, None)
else:
self.asks[price] = qty
async def load_snapshot(self):
async with aiohttp.ClientSession() as s:
url = f"https://api.binance.com/api/v3/depth?symbol={self.symbol.upper()}&limit=1000"
async with s.get(url) as r:
snap = await r.json()
self.last_update_id = snap["lastUpdateId"]
self.apply_update(snap["bids"], snap["asks"])
async def run(self):
url = f"wss://stream.binance.com:9443/ws/{self.symbol}@depth@100ms"
async with websockets.connect(url) as ws:
# ステップ 1: しばらくの間差分データを収集
asyncio.create_task(self._collect(ws))
await asyncio.sleep(1)
# ステップ 2: スナップショットを取得
await self.load_snapshot()
# ステップ 3: バッファ内の有効な差分を適用
for diff in self.buffer:
if diff["u"] < self.last_update_id:
continue
self.apply_update(diff["b"], diff["a"])
self.last_update_id = diff["u"]
self.buffer = None
# ステップ 4: 以降の差分を直接適用
async for msg in ws:
diff = json.loads(msg)
self.apply_update(diff["b"], diff["a"])
self.last_update_id = diff["u"]
best_bid = self.bids.keys()[-1] if self.bids else 0
best_ask = self.asks.keys()[0] if self.asks else 0
print(f"買一 {best_bid} 売一 {best_ask} 差 {best_ask-best_bid:.2f}")
async def _collect(self, ws):
if self.buffer is None:
return
async for msg in ws:
if self.buffer is None:
break
self.buffer.append(json.loads(msg))
ob = OrderBook("BTCUSDT")
asyncio.run(ob.run())
七、再接続処理とハートビート
バイナンスの WebSocket は、クライアントが 3 分以内に pong を返す必要があり、また接続は 24 時間で強制的に切断されます。
import asyncio, json, websockets, logging
async def resilient_ws(url, on_message, max_retries=1000):
retry = 0
while retry < max_retries:
try:
async with websockets.connect(
url,
ping_interval=180, # 3 分ごとに ping を送信
ping_timeout=10,
close_timeout=5
) as ws:
retry = 0 # 接続成功時にリセット
async for message in ws:
await on_message(json.loads(message))
except (websockets.ConnectionClosed, asyncio.TimeoutError) as e:
retry += 1
wait = min(2 ** retry, 60) # 指数バックオフ、最大 60秒
logging.warning(f"WS 切断 {e}、{wait}秒後に再接続({retry}回目)")
await asyncio.sleep(wait)
八、Node.js WebSocket の例
const WebSocket = require('ws');
const streams = ['btcusdt@bookTicker', 'ethusdt@bookTicker'].join('/');
const url = `wss://stream.binance.com:9443/stream?streams=${streams}`;
function connect() {
const ws = new WebSocket(url);
ws.on('open', () => console.log('接続成功'));
ws.on('message', (raw) => {
const { stream, data } = JSON.parse(raw);
console.log(`${data.s} 買一 ${data.b} 売一 ${data.a}`);
});
ws.on('close', () => {
console.log('切断されました。3秒後に再接続します');
setTimeout(connect, 3000);
});
ws.on('error', (err) => console.error('エラー', err.message));
}
connect();
九、よくある質問 FAQ
Q1: WebSocket にもウェイト(Weight)の消費はありますか?
A: 接続を確立する際に 2 ウェイト消費します。その後のメッセージ受信については ウェイトを全く消費しません。ただし、1 IP あたり 300 接続を超えると拒否されます。
Q2: 購読してもメッセージが届かないのはなぜですか?
A: 主に3つの原因が考えられます:1) ストリーム名は すべて小文字 である必要があります(BTCUSDT ではなく btcusdt)。2) 通貨ペア名の間違い(例:BTC-USDT ではなく BTCUSDT)。3) 購読メッセージは wss://.../ws エンドポイントに送る必要があり、/stream エンドポイントでは受け付けられません。
Q3: @depth と @depth@100ms の違いは何ですか?
A: @depth は 1000ms ごとに完全なスナップショット(20段階)を送信します。@depth@100ms は 100ms ごとに板の差分(増量データ)を送信します。ローカルでオーダーブックを維持する場合は @100ms を使い、画面表示のみなら @depth で十分です。
Q4: WebSocket で注文を出すことはできますか?
A: はい。バイナンスは注文用の WebSocket API(wss://ws-api.binance.com:443/ws-api/v3)を提供しており、REST よりも 30-50ms 低遅延で注文が可能です。ただし、エコシステムとしてはまだ成熟しておらず、主流な戦略では依然として REST での注文 + WS での監視という組み合わせが使われています。
Q5: 24 時間の自動切断をシームレスに処理するには?
A: ローリング接続方式 を推奨します。接続から 23 時間経過した時点で新しい接続 B を確立し、B でメッセージが安定して受信できるようになったら旧接続 A を閉じることで、データの欠落を回避できます。python-binance などのプロダクション級フレームワークにはこのロジックが内蔵されています。
WebSocket のソリューションを確認した後は、カテゴリナビ に戻って「API連携」カテゴリの他の SDK チュートリアルをご覧ください。