API連携

バイナンスのWebSocketで相場を購読する方法は?単一/混合ストリームのプロダクション級コード

バイナンス WebSocket リアルタイム相場の完全ソリューション:単一/混合ストリーム URL、サブスクリプションの動的管理、切断後の再接続、ハートビート、ストリームタイプ、板情報の増量マージアルゴリズムを解説。Python asyncio と Node.js の実戦コード付き。

バイナンスの 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())

六、板情報の増量マージ(ローカルでのオーダーブック維持)

高頻度トレード戦略では、ローカルでオーダーブックを維持する必要があります。手順は以下の通りです:

  1. @depth@100ms の差分ストリームを購読し、バッファに保存する。
  2. REST API の GET /api/v3/depth?symbol=X&limit=1000 でスナップショットを取得する。
  3. バッファ内の u < lastUpdateId のメッセージを破棄する。
  4. U <= lastUpdateId+1 <= u を満たすメッセージから順に適用する。
  5. 以降のメッセージを適用し続ける。この際、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 チュートリアルをご覧ください。

引き続き閲覧

バイナンスの利用でまだ疑問がありますか?カテゴリページに戻って同じテーマの他のガイドを探しましょう。

カテゴリナビ

関連ガイド

バイナンスAPIの申請方法:APIキー取得と署名生成の完全ガイド 2026-04-14 バイナンス現物API(Spot API)の使い方:ゼロから最初の注文まで、実行可能なサンプルコード 2026-04-14 バイナンスの Futures と Spot API の違いは?エンドポイント・パラメータ・ウェイトの比較 2026-04-14 バイナンスAPIでIP制限を避けるには?レート制限とウェイト計算の仕組みを解説 2026-04-14