DEV Community

kelos
kelos

Posted on

Why Does My A‑Share Market WebSocket Connection Drop Silently? Building Reliable Heartbeat & Auto‑Reconnection

dev.to metadata
Series: Quant Dev Tips
Tags: #python #websocket #api #quantitative #ashare

If you’ve built long‑running market‑data scrapers or quant clients for A‑share markets, you’ve probably hit this frustrating production bug. Everything works great locally. You deploy to a cloud server and leave it running. Hours later, ticks stop arriving. The Python process is still alive, no exceptions get raised. You only notice something is broken when trading resumes after the midday break.

Lots of developers focus heavily on writing subscription logic and parsing tick payloads. Network‑layer edge cases that only surface after hours of uptime often get overlooked. In this post I’ll walk through the root cause, practical heartbeat design rules, and share complete runnable Python code for A‑share real‑time market feeds.

What causes the "false‑alive" silent disconnect

WebSocket gives us full‑duplex persistent connections, but network traffic still passes through NAT gateways, load balancers and ISP routers. Almost all intermediate network hardware enforces idle timeout rules.

When zero packets flow for a long time, these devices clear their connection tracking table entries. Both client and server TCP stacks still think the connection is healthy, but data can no longer transmit — this is known as a false‑alive connection.

This problem is extra painful for A‑share use‑cases. There is a 90‑minute midday halt from 11:30 to 13:00 with barely any market data broadcast. Without heartbeat keep‑alive logic, connections are very likely to get killed silently during this quiet window. When the market opens again in the afternoon, your client keeps waiting and receives nothing.

Designing the heartbeat mechanism

The core workflow is straightforward:
The client sends periodic heartbeat probes and waits for server responses. If we get no reply for multiple consecutive attempts, mark the link as broken, close the socket and trigger reconnection.

💡 Important gotcha: Resubscribe symbols after every successful reconnection. Most market‑data APIs bind subscription state to the active WebSocket session. When connection drops, all subscriptions are lost. Reconnecting alone will show connected status but return zero market ticks.

Recommended parameter table

Parameter Suggested Setting Explanation
Heartbeat send interval 20‑30 seconds Keep link active, prevent intermediate devices from dropping idle sessions
Timeout threshold 3 consecutive unacknowledged heartbeats Avoid false disconnect events caused by temporary network jitter
Reconnection back‑off strategy Progressive delays: 1s, 2s, 4s, cap at 30s Prevent request flooding and reduce pressure on upstream API services
Post‑reconnection step Resubscribe full symbol watch‑list Restore market‑data subscriptions on new session

Full Python implementation

📋 Prerequisite:
pip install websocket‑client
This example uses the AllTick API A‑share WebSocket market endpoint.

import websocket
import json
import time
import threading

# ========== Configuration ==========
TOKEN = "your_token_here"
WS_URL = f"wss://quote.alltick.co/quote-stock-b-ws-api?token=yourtoken"

# List of A‑share symbols to subscribe
SYMBOLS = ["600519.SH", "000001.SZ"]


# ========== WebSocket callback handlers ==========
def on_message(ws, message):
    """Process incoming tick data and server responses"""
    try:
        data = json.loads(message)
        cmd_id = data.get("cmd_id")

        # cmd_id=22998 for real‑time A‑share tick push
        if cmd_id == 22998:
            tick_payload = data.get("data", {})
            print(f"Tick: {tick_payload.get('code')} | "
                  f"Price: {tick_payload.get('price')} | "
                  f"Volume: {tick_payload.get('volume')} | "
                  f"Time: {tick_payload.get('tick_time')}")
            # Extend your logic here: save to database, feed quant strategy
        else:
            # Print other responses such as subscription confirmation cmd_id=22005
            print("Response:", data)
    except json.JSONDecodeError as e:
        print("JSON parsing exception:", e)


def on_error(ws, error):
    print("WebSocket error:", error)


def on_close(ws, close_status_code, close_msg):
    print("WebSocket connection closed")


def on_open(ws):
    """Triggered when connection opens, send subscription and start heartbeat thread"""
    print("WebSocket connected, sending subscription request")

    subscribe_msg = {
        "cmd_id": 22004,
        "seq_id": 1,
        "trace": f"trace‑{int(time.time() * 1000)}",
        "data": {
            "symbol_list": [{"code": symbol} for symbol in SYMBOLS]
        }
    }
    ws.send(json.dumps(subscribe_msg))
    print(f"Subscribed symbols: {SYMBOLS}")

    # Background heartbeat worker thread
    def heartbeat_loop():
        while ws.sock and ws.sock.connected:
            time.sleep(10)
            try:
                ws.send("ping")
                print("Heartbeat packet sent")
            except Exception as e:
                print("Heartbeat send exception:", e)
                break

    threading.Thread(target=heartbeat_loop, daemon=True).start()


# ========== Main entry, outer loop handles auto‑reconnection ==========
if __name__ == "__main__":
    ws = websocket.WebSocketApp(
        WS_URL,
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )

    while True:
        try:
            ws.run_forever()
            print("Connection lost, retrying reconnection in 3 seconds…")
            time.sleep(3)
        except KeyboardInterrupt:
            print("Program exiting")
            break
Enter fullscreen mode Exit fullscreen mode

After deploying this snippet, I tested it through the A‑share midday market halt. The background heartbeat kept the session alive. When afternoon trading started, tick data arrived immediately with no manual restarts.

Wrap‑up

Heartbeat logic does not require massive amounts of code, yet it forces you to think about production‑only edge‑cases you will rarely reproduce on localhost: midday market break timeouts, short‑lived network flakiness, server‑side maintenance disconnects. These exact scenarios break real‑world market‑data pipelines.

A working local demo is not production‑ready.
Remember to:

  • Resubscribe instruments after every reconnection
  • Apply back‑off delays for reconnection attempts
  • Add persistent logging and alerting for live deployments

For this A‑share market‑data project I used AllTick API as my data source. Its standardized WebSocket interface lets developers focus on connection stability rather than low‑level protocol troubleshooting.

⚠️ Disclaimer: This article shares engineering experience for educational purposes only. Code examples are not investment advice.

💬 Discussion prompt

The current implementation sends heartbeat probes but does not validate incoming ping responses.
How would you modify this script to implement proper timeout detection when no heartbeat reply comes back? Feel free to share your approach in comments.

Top comments (0)