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
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)