Building a Real-Time Order Book with the Coinbase Prime API

·

In today’s fast-paced digital asset markets, having access to real-time liquidity data is essential for traders, developers, and institutions alike. The Coinbase Prime API offers a powerful solution by providing deep liquidity aggregation across multiple trading venues — not just the Coinbase Exchange. This enables users to build and maintain a real-time order book using streaming Level 2 (L2) data via WebSocket.

This guide walks you through constructing a lightweight, functional order book application in under 300 lines of Python code. We’ll use Pandas for data processing, SQLite for local storage, and Dash to create a responsive frontend that displays live snapshots of the order book.

Whether you're building algorithmic trading systems or simply exploring market microstructure, this tutorial provides a clear foundation for working with real-time financial data.


How the Coinbase Prime Order Book Works

The Coinbase Prime platform aggregates liquidity from various exchanges through its Smart Order Router (SOR). This means the order book isn't limited to Coinbase’s own exchange but reflects combined depth across connected venues.

This unified view is delivered via the l2_data WebSocket feed, which streams two types of messages:

To maintain an accurate local order book, your application must:

  1. Receive and store the initial snapshot.
  2. Apply each subsequent update to reflect real-time changes.

This approach ensures low latency and high fidelity without overwhelming your system with redundant data.


Step 1: Connecting to the WebSocket Feed

Start by creating a file called backend.py. This script handles authentication and real-time data ingestion from the Prime API.

You'll need the following imports:

import asyncio
import websockets
import sqlite3
import json
import hmac
import hashlib
import base64
import time
import sys
from dotenv import load_dotenv
from orderbook import OrderBookProcessor

Install missing packages using pip:

pip install websockets python-dotenv pandas dash

Set Up Authentication

To authenticate with Coinbase Prime, retrieve your credentials from the Prime UI and assign them as follows:

ACCESS_KEY = 'your_access_key'
SECRET_KEY = 'your_secret_key'
PASSPHRASE = 'your_passphrase'
SVC_ACCOUNTID = 'your_account_id'
🔐 For security, store these in a .env file and load them using load_dotenv().

Define the connection parameters:

URI = 'wss://ws-feed.prime.coinbase.com'
timestamp = str(int(time.time()))
channel = 'l2_data'
conn = sqlite3.connect('prime_orderbook.db', check_same_thread=False)

Customize the trading pair, aggregation level, and display depth:

product_id = 'ETH-USD'
agg_level = '0.1'   # Price bin size in USD
row_count = '50'    # Number of rows to display

👉 See how professional traders manage real-time data feeds efficiently.

Main Event Loop

The core loop connects to the WebSocket, authenticates, and processes incoming messages:

async def main_loop():
    processor = None
    while True:
        try:
            async with websockets.connect(URI, ping_interval=None, max_size=None) as ws:
                auth_msg = await create_auth_message(channel, product_id, timestamp)
                await ws.send(auth_msg)

                async for response in ws:
                    parsed = json.loads(response)

                    if parsed['channel'] == 'l2_data':
                        event_type = parsed['events'][0]['type']
                        if event_type == 'snapshot':
                            processor = OrderBookProcessor(response)
                        elif processor:
                            processor.apply_update(response)
                            table = processor.create_df(agg_level=agg_level)
                            table.to_sql('book', conn, if_exists='replace', index=False)
        except websockets.ConnectionClosed:
            continue

Authentication uses HMAC-SHA256 signing:

def sign(message, secret):
    return base64.b64encode(
        hmac.new(secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).digest()
    ).decode()

async def create_auth_message(channel, product_id, timestamp):
    message = channel + ACCESS_KEY + SVC_ACCOUNTID + timestamp + product_id
    signature = sign(message, SECRET_KEY)
    return json.dumps({
        'type': 'subscribe',
        'channel': channel,
        'access_key': ACCESS_KEY,
        'api_key_id': SVC_ACCOUNTID,
        'timestamp': timestamp,
        'passphrase': PASSPHRASE,
        'signature': signature,
        'product_ids': [product_id],
    })

Initialize the script:

if __name__ == '__main__':
    asyncio.run(main_loop())

Step 2: Processing Order Book Data

Create orderbook.py to manage the local state of bids and asks.

Core Functions Overview

The OrderBookProcessor class includes:

Aggregation Logic Explained

Without aggregation, order books can have thousands of price levels — making them hard to interpret. By grouping prices into bins (e.g., every $0.10), we simplify the view while preserving liquidity visibility.

Bids use the minimum price in the bin; asks use the maximum, ensuring correct taker pricing logic.

For example:

This prevents slippage misinterpretation when placing limit orders.


Step 3: Building a Real-Time Frontend with Dash

Now create frontend.py to visualize the order book.

import sqlite3
import pandas as pd
from dash import html, dcc, Dash, dash_table, Input, Output
from backend import product_id, row_count

Set up the app layout:

app = Dash(__name__)

app.layout = html.Div([
    html.H1(id='mid-price', style={'text-align': 'center'}),
    dcc.Interval(id='update', interval=1000),  # Update every second
    dash_table.DataTable(
        id='my-table',
        columns=[
            {'name': 'Price', 'id': 'px', 'type': 'numeric'},
            {'name': 'Quantity', 'id': 'qty', 'type': 'numeric'}
        ],
        style_data_conditional=[
            {
                'if': {'filter_query': '{id} contains "bid"', 'column_id': 'px'},
                'backgroundColor': '#50C878',
                'color': 'white'
            },
            {
                'if': {'filter_query': '{id} contains "ask"', 'column_id': 'px'},
                'backgroundColor': '#DC143C',
                'color': 'white'
            }
        ],
        style_cell={'fontFamily': 'Courier New', 'textAlign': 'center'}
    )
], style={'width': '40%', 'margin': 'auto'})

Load data from SQLite:

def load_data():
    conn = sqlite3.connect('prime_orderbook.db')
    query = f"SELECT * FROM book ORDER BY CAST(SUBSTR(id, 1, INSTR(id, '_')-1) AS INTEGER) LIMIT {row_count}"
    df = pd.read_sql_query(query, conn)
    return df.sort_values(by='px', ascending=False)

Update UI dynamically:

@app.callback(Output('mid-price', 'children'), Input('update', 'n_intervals'))
def update_header(n):
    df = load_data()
    best_bid = df[df['id'].str.contains('bid')]['px'].max()
    return f"{product_id} | Best Bid: ${best_bid:,.2f}"

@app.callback(Output('my-table', 'data'), Input('update', 'n_intervals'))
def update_table(n):
    return load_data().to_dict('records')

Run the app:

python backend.py    # In one terminal
python frontend.py   # In another

👉 Discover how advanced trading platforms handle real-time market data at scale.


Frequently Asked Questions (FAQ)

Q: What is an L2 order book?
A: Level 2 (L2) data shows aggregated price levels and quantities on both bid and ask sides of the market, offering deeper insight than simple top-of-book quotes.

Q: Why use aggregation in an order book?
A: Aggregation reduces noise by grouping similar prices into bins (e.g., $0.10 increments), making it easier to identify support/resistance zones and improving UX.

Q: Can I connect to multiple products simultaneously?
A: Yes — modify the product_ids array in the subscription message to include multiple pairs like ['ETH-USD', 'BTC-USD'].

Q: Is this setup suitable for production trading?
A: While functional for monitoring and research, production systems require additional resilience: reconnection logic, message sequencing checks, and failover mechanisms.

Q: How often does the WebSocket send updates?
A: Updates are sent in real time — typically within milliseconds of market events. The frequency depends on market activity.

Q: What happens if I miss a message?
A: Missing messages can desynchronize your local book. Always validate sequence numbers if available and consider implementing checksum reconciliation.


Final Thoughts

By combining Coinbase Prime’s robust WebSocket feed with efficient data processing in Python, you can build a fully functional real-time order book in minutes. This foundation supports further enhancements like trade execution, charting integration, or custom alerting systems.

Whether you're a quant developer or a fintech enthusiast, mastering real-time market data opens doors to smarter decision-making and automated strategies.

👉 Explore next-generation trading tools powered by real-time data analytics.