DEV Community

EmilyL
EmilyL

Posted on

How to Batch Subscribe to 5000+ A-Share Real-Time Quotes with a Single WebSocket (Python)

When I first tackled real-time market data for the entire A-share market, I did what many devs do: I wrote a loop that polled a REST endpoint for each stock. It was slow. It was fragile. And it was constantly hitting rate limits.

The solution? Tear down the polling loop and put up a WebSocket subscription. This post walks through why you should too, and shows you the code to get started.

The Problem with Polling 5000+ Instruments

Polling is a synchronous ask-reply model. You request, you wait, you get a response. When multiplied by thousands of stocks and the high tick frequency of A-shares (hundreds of trades per second during peak), you end up with a stream of delayed snapshots, not a real-time feed.

WebSocket push turns the model on its head. The server sends you data only when something happens, over a persistent connection. This means:

  • Millisecond-level latency
  • No wasted requests or rate limits
  • One TCP connection regardless of how many symbols you track

Two Common Subscription Message Formats

Different APIs accept slightly different shapes. Usually it's either:

Format Sample Use Case
Array ["000001","000002","600036"] Great for dynamic watchlists
String "000001,000002,600036" Compact, easy for URL params

Some providers also let you use a wildcard to subscribe to all listed A-shares — amazing for scanners, but you’ll need to get the appropriate access tier.

Code: One WebSocket, Ten Stocks, Zero Polling

Here’s a basic Python script using websocket-client. It connects to a push endpoint (think AllTick-style APIs) and subscribes to a batch of symbols.

import websocket
import json

# WebSocket endpoint that streams real-time trade ticks
url = "wss://apis.alltick.co/websocket-api/stock-websocket-interface-api/transaction-quote-subscription"

def on_message(ws, message):
    # Decode the incoming JSON
    data = json.loads(message)
    # Iterate over all tick updates
    for tick in data.get("ticks", []):
        print(f"Code:{tick['code']} Price:{tick['price']} Time:{tick['time']}")

def on_open(ws):
    # Batch subscription request for 10 A-shares
    sub_msg = {
        "action": "subscribe",
        "symbols": ["000001", "000002", "600036", "600519", "000858",
                    "002415", "300750", "601318", "000333", "002594"]
    }
    ws.send(json.dumps(sub_msg))

ws = websocket.WebSocketApp(url, on_message=on_message)
ws.on_open = on_open
ws.run_forever()
Enter fullscreen mode Exit fullscreen mode

Run this, and every time any of those stocks trades, you’ll see the price, code, and timestamp instantly.

Surviving the Data Firehose

Once you scale to the full market, you might see hundreds of ticks per second. Here’s how I handle it in production:

  • Sharded processing: Hash by stock code and feed separate worker queues.
  • Snapshot cache: Keep a dict of latest prices for O(1) read access.
  • Batch DB inserts: Accumulate ticks and flush in micro-batches (e.g., 100 records or every 200ms).
  • Threshold-based UI updates: Only push price changes larger than a set tick size to front-end clients.

Preparing for All-Market Subscription

If you’re going to scan the entire market, make sure:

  • Your network and CPU can handle peak loads (I’ve logged ~300–400 ticks/sec during openings).
  • You introduce a buffer (like Redis Streams or Kafka) when processing can’t keep up.
  • You enable server-side filters that send ticks only for stocks with actual trades.

Wrapping Up

Stop polling. Start pushing. A single WebSocket with batch subscription is the most efficient way I’ve found to get full A-share tick data. When evaluating a provider, focus on latency, batch support, and stability. Then start small, stress-test, and scale gradually.

Your future self — and your servers — will thank you.

Top comments (0)