Alpaca Crypto WebSocket: Real-Time Market Data Guide
Learn how to use Alpaca's crypto WebSocket API to stream live price data, build trading bots, and react to market moves in milliseconds.
Learn how to use Alpaca's crypto WebSocket API to stream live price data, build trading bots, and react to market moves in milliseconds.
If you've ever watched a price spike on Binance and wished your bot had caught it three seconds earlier, the problem probably wasn't your strategy — it was your data pipeline. REST polling introduces latency that kills edge. WebSocket connections solve that by pushing data to you the moment it exists on the exchange. Alpaca's crypto WebSocket API is one of the cleaner implementations available to retail algo traders today, and this guide walks through everything you need to get live market data flowing into your system.
Alpaca is a commission-free brokerage and market data provider that offers both REST and WebSocket APIs for crypto and equities. Their crypto data feed connects to major liquidity sources and normalizes tick data across venues — so instead of maintaining separate connections to Binance, Coinbase, or Bybit, you get a unified stream through one authenticated socket.
The WebSocket endpoint streams three primary data types: trades (individual executed transactions), quotes (best bid/ask at any moment), and bars (OHLCV candles at configurable intervals). For algo traders, quotes and trades are the most valuable — they let you react to microstructure changes before they're reflected in candle closes.
Alpaca offers two tiers: a free basic feed and a paid 'unlimited' feed. The free tier has some data delays and limitations on simultaneous subscriptions. For serious algotrading, the paid tier is worth the cost — stale quotes are worse than no quotes.
Authentication happens over the WebSocket connection itself, not via HTTP headers. After establishing the connection, you send a JSON auth message with your API key and secret. Here's a minimal Python connection using the websockets library:
import asyncio
import json
import websockets
API_KEY = "your_api_key_here"
API_SECRET = "your_api_secret_here"
WS_URL = "wss://stream.data.alpaca.markets/v1beta3/crypto/us"
async def connect():
async with websockets.connect(WS_URL) as ws:
# Receive welcome message
msg = await ws.recv()
print("Server:", json.loads(msg))
# Authenticate
auth_payload = {
"action": "auth",
"key": API_KEY,
"secret": API_SECRET
}
await ws.send(json.dumps(auth_payload))
auth_response = await ws.recv()
print("Auth:", json.loads(auth_response))
asyncio.run(connect())
A successful authentication returns a message with status 'authenticated'. If you see 'auth failed', double-check that you're using crypto-enabled API keys — Alpaca issues separate keys for paper trading, live trading, and market data. Many developers waste an hour on this.
Once authenticated, you subscribe to specific symbols. Alpaca uses their own symbol format — Bitcoin is 'BTC/USD', Ethereum is 'ETH/USD'. You can subscribe to trades, quotes, and bars independently, and you can use wildcards ('*') to subscribe to everything, though that generates significant message volume.
import asyncio
import json
import websockets
API_KEY = "your_api_key_here"
API_SECRET = "your_api_secret_here"
WS_URL = "wss://stream.data.alpaca.markets/v1beta3/crypto/us"
async def stream_crypto():
async with websockets.connect(WS_URL) as ws:
# Welcome
await ws.recv()
# Auth
await ws.send(json.dumps({
"action": "auth",
"key": API_KEY,
"secret": API_SECRET
}))
await ws.recv()
# Subscribe to BTC and ETH trades + quotes
subscribe_payload = {
"action": "subscribe",
"trades": ["BTC/USD", "ETH/USD"],
"quotes": ["BTC/USD", "ETH/USD"],
"bars": ["BTC/USD"]
}
await ws.send(json.dumps(subscribe_payload))
sub_response = await ws.recv()
print("Subscribed:", json.loads(sub_response))
# Listen for messages
while True:
try:
message = await ws.recv()
data = json.loads(message)
for item in data:
msg_type = item.get("T")
if msg_type == "t": # trade
print(f"TRADE {item['S']}: {item['p']} x {item['s']}")
elif msg_type == "q": # quote
print(f"QUOTE {item['S']}: bid={item['bp']} ask={item['ap']}")
elif msg_type == "b": # bar
print(f"BAR {item['S']}: O={item['o']} H={item['h']} L={item['l']} C={item['c']}")
except websockets.exceptions.ConnectionClosed:
print("Connection closed, reconnecting...")
break
asyncio.run(stream_crypto())
Messages arrive as JSON arrays — always iterate over the list, not treat it as a single object. Alpaca batches multiple events in one WebSocket frame during high-volume periods.
Understanding the field abbreviations is essential. Alpaca compresses field names to reduce bandwidth on high-frequency streams. Here's the complete reference for each message type:
| Field | Type | Description |
|---|---|---|
| T | string | Message type: t=trade, q=quote, b=bar, s=subscription, error |
| S | string | Symbol (e.g. BTC/USD) |
| p | float | Trade price |
| s | float | Trade size |
| t | timestamp | Event timestamp (RFC3339) |
| bp | float | Bid price (quotes only) |
| ap | float | Ask price (quotes only) |
| bs | float | Bid size (quotes only) |
| as | float | Ask size (quotes only) |
| o/h/l/c | float | Open/High/Low/Close (bars only) |
| v | float | Volume (bars only) |
One thing that trips up developers: the 't' field name is used for both the message type identifier ('T' uppercase) and the timestamp field ('t' lowercase). Python's json parser handles case sensitivity correctly, but worth being explicit in your parsing logic.
Production WebSocket clients need robust error handling. Connections drop — Alpaca's infrastructure, your ISP, or anything in between can interrupt the stream. A bot that doesn't reconnect is a bot that misses trades. Here's a production-grade wrapper with exponential backoff:
import asyncio
import json
import websockets
import logging
from datetime import datetime
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
API_KEY = "your_api_key_here"
API_SECRET = "your_api_secret_here"
WS_URL = "wss://stream.data.alpaca.markets/v1beta3/crypto/us"
SYMBOLS = ["BTC/USD", "ETH/USD", "SOL/USD"]
async def authenticate(ws):
await ws.recv() # welcome
await ws.send(json.dumps({
"action": "auth",
"key": API_KEY,
"secret": API_SECRET
}))
response = json.loads(await ws.recv())
if response[0].get("msg") != "authenticated":
raise Exception(f"Auth failed: {response}")
logger.info("Authenticated successfully")
async def subscribe(ws, symbols):
await ws.send(json.dumps({
"action": "subscribe",
"trades": symbols,
"quotes": symbols
}))
await ws.recv() # subscription confirmation
logger.info(f"Subscribed to: {symbols}")
def handle_message(item):
msg_type = item.get("T")
symbol = item.get("S", "")
ts = item.get("t", "")
if msg_type == "t":
logger.info(f"{ts} TRADE {symbol}: price={item['p']}, size={item['s']}")
# Your signal logic here — e.g., feed into VoiceOfChain or your own strategy engine
elif msg_type == "q":
spread = round(item['ap'] - item['bp'], 4)
logger.info(f"{ts} QUOTE {symbol}: spread={spread}")
async def run_stream(retry_count=0):
max_retries = 10
backoff = min(2 ** retry_count, 60) # cap at 60s
if retry_count > 0:
logger.info(f"Reconnecting in {backoff}s (attempt {retry_count}/{max_retries})")
await asyncio.sleep(backoff)
try:
async with websockets.connect(
WS_URL,
ping_interval=20,
ping_timeout=10
) as ws:
await authenticate(ws)
await subscribe(ws, SYMBOLS)
retry_count = 0 # reset on successful connection
while True:
message = await ws.recv()
for item in json.loads(message):
handle_message(item)
except (websockets.exceptions.ConnectionClosed,
websockets.exceptions.WebSocketException,
ConnectionResetError) as e:
logger.warning(f"Connection error: {e}")
if retry_count < max_retries:
await run_stream(retry_count + 1)
else:
logger.error("Max retries exceeded")
asyncio.run(run_stream())
Notice the ping_interval parameter in the websockets.connect call. Without it, idle connections get killed by network equipment after a few minutes. Alpaca expects keepalives — setting ping_interval to 20 seconds keeps the connection alive during slow market periods.
Raw tick data is noise. The value comes from what you do with it. Platforms like Bybit and OKX provide their own WebSocket feeds, but they require exchange-specific integrations. With Alpaca, you get normalized data you can feed directly into signal logic without exchange-specific adapters.
One practical pattern: use the Alpaca WebSocket for price monitoring and combine it with signals from a platform like VoiceOfChain, which aggregates on-chain and exchange data to generate actionable trading alerts. When VoiceOfChain fires a signal on a token, your Alpaca stream already has the price context — you know the current bid/ask spread, recent trade volume, and whether price is moving toward or away from the signal level before you enter.
For traders who also use Coinbase Advanced Trade or Binance for execution, a common architecture looks like: Alpaca WebSocket for price feeds → signal evaluation layer → execution via exchange-native API. This decouples data from execution, which makes the system easier to maintain and test.
The Alpaca crypto WebSocket API gives retail algo traders access to real-time market data without the complexity of managing exchange-specific connections. The authentication flow is clean, the message schema is consistent, and with proper reconnection logic, you can build stable streaming infrastructure in an afternoon.
The code patterns in this guide — authenticated connections, symbol subscription, message parsing, and exponential backoff reconnection — form the backbone of any production WebSocket client. Adapt them to your strategy: whether you're monitoring spreads, reacting to trade volume spikes, or feeding signals from VoiceOfChain into automated execution on Binance or Coinbase, the underlying socket architecture stays the same.
Start with two or three symbols you actively trade. Get the data flowing, log it, understand its rhythm. Then build your logic on top of a stream you trust.