Guía de WebSocket en Python para la API de Backpack Exchange

Esta guía demuestra cómo usar la API WebSocket de Backpack Exchange con Python. Los WebSockets proporcionan flujos de datos en tiempo real para datos de mercado y actualizaciones de cuenta.

Prerequisitos

Obtén tus claves API si vas a usar streams privados: https://backpack.exchange/portfolio/settings/api-keys

Instala las librerías de Python necesarias:

  • websockets - para conexiones WebSocket

  • cryptography - para X-Signature (solo streams privados)

pip install websockets cryptography

Instala dotenv-python para gestionar de forma segura tus claves usando variables de entorno si vas a usar streams privados

pip install python-dotenv

Crea un archivo .env y almacena tus claves así:

PUBLIC_KEY=zDIJj9qneWIY0IYZ5aXoHcNMCm+XDhVcTssiT0HyY0A=
SECRET_KEY=4odxgSUxFrC/zsKWZF4OQwYAgnNu9hnWH3NxWfLAPz4=

Crea un archivo .gitignore y añade .env para excluirlo del control de versiones.

.env

Importa las librerías necesarias:

import json
import asyncio
import websockets
import base64
from time import time
import os
from cryptography.hazmat.primitives.asymmetric import ed25519
from dotenv import load_dotenv, find_dotenv

Básicos de la API WebSocket

La API WebSocket de Backpack Exchange está disponible en wss://ws.backpack.exchange.

Los streams WebSocket se nombran usando el formato: <type>.<symbol>

Por ejemplo:

  • depth.SOL_USDC - Libro de órdenes para SOL/USDC

  • trade.SOL_USDC - Trades para SOL/USDC

Por qué usar Async con WebSockets

Los WebSockets están diseñados para conexiones de larga duración que reciben datos en tiempo real. Usar programación asíncrona con WebSockets ofrece varias ventajas:

  1. E/S no bloqueante: Async permite que tu aplicación maneje múltiples conexiones sin bloquear el hilo principal.

  2. Eficiencia de recursos: Async usa menos recursos que crear múltiples hilos para conexiones concurrentes.

  3. Mejor rendimiento: Async puede manejar muchas conexiones con menos overhead que enfoques síncronos.

  4. Procesamiento en tiempo real: Async es ideal para flujos de datos en tiempo real donde necesitas recibir y procesar datos continuamente.

Desventajas de los enfoques síncronos:

  1. Threading complejo: Requiere gestión manual de hilos

  2. Intensivo en recursos: Cada conexión necesita su propio hilo

  3. Manejo de errores difícil: La propagación de errores entre hilos es compleja

  4. Problemas de escalabilidad: No escala bien con muchas conexiones

El enfoque async (como se muestra en nuestros ejemplos) es mucho más limpio, eficiente y fácil de mantener.

Streams públicos

Los streams públicos no requieren autenticación. Puedes suscribirte a ellos directamente.

Ejemplo: Suscribiéndose a un stream público

async def subscribe_to_public_stream():
    uri = "wss://ws.backpack.exchange"

    async with websockets.connect(uri) as websocket:
        # Subscribe to the depth stream for SOL/USDC
        subscribe_message = {
            "method": "SUBSCRIBE",
            "params": ["depth.SOL_USDC"]
        }

        await websocket.send(json.dumps(subscribe_message))
        print(f"Subscribed to depth.SOL_USDC stream")

        # Process incoming messages
        while True:
            response = await websocket.recv()
            data = json.loads(response)
            print(f"Received: {data}")

            # You can process the data here based on your needs
            # For example, update a local order book

# To run the async function in a Jupyter notebook, use:
# await subscribe_to_public_stream()
#
# TO run in your code
# asyncio.run(subscribe_to_public_stream())  

Ejemplo: Suscribiéndose a múltiples streams públicos

async def subscribe_to_multiple_streams():
    uri = "wss://ws.backpack.exchange"

    async with websockets.connect(uri) as websocket:
        # Subscribe to multiple streams
        subscribe_message = {
            "method": "SUBSCRIBE",
            "params": ["depth.SOL_USDC", "trade.SOL_USDC"]
        }

        await websocket.send(json.dumps(subscribe_message))
        print(f"Subscribed to multiple streams")

        # Process incoming messages
        while True:
            response = await websocket.recv()
            data = json.loads(response)
            print(f"Received: {data}")

            # Process different stream data based on the stream name
            if "stream" in data and "data" in data:
                stream_name = data["stream"]
                stream_data = data["data"]

                if stream_name.startswith("depth."):
                    # Process order book data
                    print(f"Order book update: {stream_data}")
                elif stream_name.startswith("trade."):
                    # Process trade data
                    print(f"Trade update: {stream_data}")

# Run the async function in a Jupyter notebook:
# await subscribe_to_multiple_streams()
#
# To run in your code
# asyncio.run(subscribe_to_multiple_streams())

Streams privados

Los streams privados requieren autenticación con tus claves API. Estos streams tienen el prefijo account. y proporcionan actualizaciones sobre tu cuenta.

Autenticación para streams privados

Para autenticarte para streams privados, necesitas:

  1. Crear un string de firma de la forma: instruction=subscribe&timestamp=1614550000000&window=5000

  2. Firmarlo con tu clave privada

  3. Incluir los datos de firma en tu mensaje de suscripción como un array: "signature": ["<verifying key>", "<signature>", "<timestamp>", "<window>"]

Los streams privados tienen el prefijo account. y requieren que se envíen datos de firma en los parámetros de suscripción. La clave verificadora y la firma deben estar codificadas en base64.

# Load API keys from .env file
# load_dotenv(find_dotenv())
# public_key = os.getenv("PUBLIC_KEY")
# secret_key = os.getenv("SECRET_KEY")

# For demonstration purposes only - don't hardcode keys in production
public_key = "5+yQgwU0ZdJ/9s+GXfuPFfo7yQQpl9CgvQedJXne30o="
secret_key = "TDSkv44jf/iD/QCKkyCdixO+p1sfLXxk+PZH7mW/ams="

# Create private key from secret key
private_key = ed25519.Ed25519PrivateKey.from_private_bytes(
    base64.b64decode(secret_key)
)

Ejemplo: Suscribiéndose a un stream privado

async def subscribe_to_private_stream():
    uri = "wss://ws.backpack.exchange"

    # Generate authentication parameters
    timestamp = int(time() * 1e3)  # Unix time in milliseconds
    window = "5000"  # Time window in milliseconds

    # Create signature string
    sign_str = f"instruction=subscribe&timestamp={timestamp}&window={window}"

    # Sign the string
    signature_bytes = private_key.sign(sign_str.encode())
    encoded_signature = base64.b64encode(signature_bytes).decode()

    async with websockets.connect(uri) as websocket:
        # Subscribe to the order stream with authentication
        subscribe_message = {
            "method": "SUBSCRIBE",
            "params": ["account.orderUpdate"],
            "signature": [public_key, encoded_signature, str(timestamp), window]
        }

        await websocket.send(json.dumps(subscribe_message))
        print(f"Subscribed to account.order stream")

        # Process incoming messages
        while True:
            response = await websocket.recv()
            data = json.loads(response)
            print(f"Received: {data}")

            # Process order updates
            if "stream" in data and data["stream"] == "account.order" and "data" in data:
                order_data = data["data"]
                print(f"Order update: {order_data}")

# Run the async function in a Jupyter notebook:
await subscribe_to_private_stream()
# 
# If using nest_asyncio (recommended):
# asyncio.run(subscribe_to_private_stream())

WebSocket Ping/Pong

Las conexiones WebSocket requieren un mecanismo ping-pong para mantener la conexión activa. La buena noticia es que la librería websockets de Python maneja esto automáticamente

Fuentes

Para más información visita la documentación oficial: https://docs.backpack.exchange/#tag/Streams

Last updated