KhueApps
Home/Python/Fetch Real-Time Bitcoin Price in Python with asyncio

Fetch Real-Time Bitcoin Price in Python with asyncio

Last updated: October 03, 2025

Overview

This guide shows how to fetch real-time Bitcoin (BTC) prices in Python using async/await. We’ll use aiohttp for a WebSocket stream (low latency) and a REST fallback (simple polling). Examples target BTC/USDT from a public endpoint suitable for prototyping algorithmic trading pipelines.

Why async?

  • Non-blocking I/O: handle networking without stalling your event loop.
  • Easy concurrency: run multiple streams/tasks in one process.
  • Low overhead: good for live feeds, order routing, and logging.

WebSocket vs REST

MethodProsConsUse when
WebSocketReal-time, low latencyRequires reconnect handlingLive price streams/triggers
RESTSimple, statelessPolling latency and rate limitsHealth checks, fallbacks

Quickstart

  1. Requirements
  • Python 3.10+
  • aiohttp
  1. Install
pip install aiohttp
  1. Run the minimal example below to stream BTC price ticks.

  2. Optional: add the REST fallback snippet to recover when the socket drops.

Minimal Working Example (WebSocket streaming)

This connects to a public trade stream and prints the latest BTC price in USDT.

import asyncio
import json
import time
from decimal import Decimal
import aiohttp

WS_URL = "wss://stream.binance.com:9443/ws/btcusdt@trade"  # BTC/USDT trades

async def stream_btc_prices():
    backoff = 1
    max_backoff = 30
    async with aiohttp.ClientSession() as session:
        while True:
            try:
                async with session.ws_connect(WS_URL, heartbeat=30, ssl=True) as ws:
                    print("Connected to WebSocket")
                    backoff = 1  # reset on successful connect
                    async for msg in ws:
                        if msg.type == aiohttp.WSMsgType.TEXT:
                            data = json.loads(msg.data)
                            # Price string like "p": "43210.12"
                            price = Decimal(data.get("p"))
                            event_ts_ms = data.get("E")  # exchange event time
                            local_ts = time.time()
                            print(f"{local_ts:.3f} | BTCUSDT = {price}")
                        elif msg.type == aiohttp.WSMsgType.ERROR:
                            raise RuntimeError(f"WebSocket error: {msg.data}")
                        elif msg.type == aiohttp.WSMsgType.CLOSED:
                            raise RuntimeError("WebSocket closed by server")
            except Exception as e:
                print(f"Disconnected: {e}. Reconnecting in {backoff}s...")
                await asyncio.sleep(backoff)
                backoff = min(max_backoff, backoff * 2)

if __name__ == "__main__":
    try:
        asyncio.run(stream_btc_prices())
    except KeyboardInterrupt:
        print("Stopped")

Notes:

  • The stream emits a message per trade; you always see the latest traded price.
  • Decimal avoids floating-point rounding issues common in trading systems.

Numbered Steps (from zero to live)

  1. Choose a market
  • Use a liquid market: BTC/USDT is widely available and usually high-liquidity.
  1. Pick endpoints
  • WebSocket stream of trades for low-latency ticks.
  • REST endpoint for fallback and health checks.
  1. Structure your event loop
  • One task for the socket reader.
  • Optional task for REST heartbeat or metrics.
  • Use asyncio.run() to manage lifecycle.
  1. Parse incoming messages
  • Extract price and event timestamps.
  • Normalize to Decimal for precision.
  1. Resilience
  • Exponential backoff on disconnects.
  • Heartbeat/ping via ws_connect(heartbeat=...).
  1. REST fallback (polling)
  • Poll when the stream is down or to verify last price.
  1. Integrate with your strategy
  • Push prices into an asyncio.Queue for downstream consumers (signal generation, risk checks, execution).

REST Fallback (polling)

Use this to fetch the latest price when WebSocket is unavailable or for periodic verification.

import asyncio
from decimal import Decimal
import aiohttp

REST_URL = "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"

async def fetch_btc_price_rest(session: aiohttp.ClientSession) -> Decimal:
    async with session.get(REST_URL, timeout=aiohttp.ClientTimeout(total=5)) as resp:
        resp.raise_for_status()
        data = await resp.json()
        return Decimal(data["price"])

async def main_rest_example():
    async with aiohttp.ClientSession() as session:
        price = await fetch_btc_price_rest(session)
        print(f"REST BTCUSDT = {price}")

if __name__ == "__main__":
    asyncio.run(main_rest_example())

Tip: Throttle polling (e.g., once per 1–5 seconds) to respect rate limits.

Combining Stream + Queue for Strategies

Push prices to a queue to decouple ingestion from strategy logic.

import asyncio
import json
from decimal import Decimal
import aiohttp

WS_URL = "wss://stream.binance.com:9443/ws/btcusdt@trade"

async def price_producer(q: asyncio.Queue):
    async with aiohttp.ClientSession() as session:
        async with session.ws_connect(WS_URL, heartbeat=30, ssl=True) as ws:
            async for msg in ws:
                data = json.loads(msg.data)
                price = Decimal(data["p"])
                await q.put(price)

async def strategy_consumer(q: asyncio.Queue):
    while True:
        price = await q.get()
        # Example: simple momentum trigger placeholder
        # Replace with your strategy logic
        print(f"Strategy received price: {price}")
        q.task_done()

async def main():
    q = asyncio.Queue(maxsize=1000)
    producer = asyncio.create_task(price_producer(q))
    consumer = asyncio.create_task(strategy_consumer(q))
    await asyncio.gather(producer, consumer)

if __name__ == "__main__":
    asyncio.run(main())

Pitfalls and How to Avoid Them

  • Pair confusion: BTCUSDT (USDT) is not BTCUSD (fiat USD). Pick the correct symbol for your PnL and hedging.
  • Floats vs Decimal: Use Decimal for prices/quantities to avoid rounding drift.
  • Reconnect storms: Implement capped exponential backoff (e.g., max 30s) and jitter if you scale horizontally.
  • Time handling: Exchange timestamps differ from local time; don’t assume clock sync. Use event time for sequencing and NTP-sync your host.
  • Rate limits: REST endpoints have limits; cache and throttle. WebSocket may kick you for excessive reconnects.
  • Regional availability: Endpoints can differ by region or compliance. Validate the exact host and symbol names.
  • Data gaps: During disconnects you miss trades. For trading-critical logic, consider redundant feeds and reconciliation.

Performance Notes

  • Single dependency: aiohttp handles both REST and WebSocket to keep footprint small.
  • Event loop: Keep handlers lightweight; offload heavy math to worker tasks or processes.
  • JSON parsing: Avoid unnecessary copies; parse once and extract only needed fields.
  • Backpressure: Use asyncio.Queue with bounded size to prevent memory bloat.
  • Logging: Rate-limit logs in hot paths; defer formatting or use structured logs.
  • Batching: For analytics, batch prices or sample at intervals to reduce CPU load.
  • Multiplexing: For multiple pairs/exchanges, run one task per stream; use gather and cancellation on shutdown.

Tiny FAQ

  • Do I need an API key?

    • Not for public ticker/trade streams shown here.
  • How “real-time” is this?

    • Sub-second trade updates in practice; network and exchange load affect latency.
  • Can I get USD instead of USDT?

    • Subscribe to a USD-quoted pair if the venue lists it; otherwise convert using a USD index.
  • What if the WebSocket drops?

    • The example auto-reconnects with backoff. Use the REST fallback to sanity-check the last price.
  • How do I scale to multiple exchanges?

    • Spin one async task per feed and normalize messages to a common schema before strategy logic.

Next Article: Generating Real-Time Trading Signals with yfinance and Python

Series: Algorithmic Trading with Python

Python