DEV Community

kelos
kelos

Posted on

Python Precious Metal API Guide: Tick Stream Time Alignment + Dynamic Subscription To Fix All Data Collection Issues

Intro

If you’re building quantitative trading scripts or real-time market data scrapers for precious metals, you’ve definitely run into annoying recurring bugs.

Every time you switch tracking between gold and silver instruments, rebuilding WebSocket connections creates data gaps during high-impact events like NFP or rate announcements. Misaligned timestamps create jagged 1-minute candlesticks that make backtesting results completely inconsistent with live market data. Running multiple assets in parallel also spikes RAM and bandwidth usage, causing constant program lag.

After lots of trial and error, I built a complete local Python pipeline built around AllTick API. No cloud servers required — it works perfectly on regular laptops and school lab machines. The code is fully annotated, with built-in logic to fix out-of-order tick alignment, persistent long-lived WebSocket dynamic subscriptions, and safeguards for the most common runtime bugs. This script fits coursework, personal backtesting projects, and lightweight local market data collectors.

Common Pain Points When Building Market Data Tools

Repeated WebSocket Rebuilds Waste Resources & Create Data Gaps

Recreating WebSocket connections every time you add or remove a metal asset triggers connection storms during volatile markets. The empty window between disconnect and reconnect deletes raw tick data entirely. Combined with network latency and out-of-order server forwarding, timestamps drift heavily between exchange native event time and local machine reception time, warping short-term candlesticks and making backtests unreproducible.

Unordered Tick Floods Cause Program Lag

Massive volumes of unsorted ticks clog callback threads. Single-asset collection has minimal overhead, but parallel tracking for multiple precious metals drastically increases CPU and memory load, slowing down debugging and backtesting workflows.

Candlestick Aggregation Using Local System Time Produces Distorted Charts

Market APIs deliver three separate timestamps: exchange generation time, server relay time, local reception time. Price volatility widens gaps between these three values. If you build candlesticks purely using your machine’s clock, high/low price points get misplaced, rendering charts useless for analysis.

Subscription Race Conditions Create "Ghost Subscription" Hidden Bugs

Quickly adding and removing assets scrambles WebSocket command order. The subscription list stored in local memory falls out of sync with the server’s active tracking list, leading to two frustrating outcomes: receiving zero data for target symbols, or duplicated tick streams that waste bandwidth and complicate analysis. These invisible bugs take hours to debug.

Limited Aggregation Metrics Hinder Volatility Spotting

Most basic implementations only calculate standard OHLC values, without VWAP and tick transaction density. Without these auxiliary metrics, it’s hard to spot early volatility spikes and implement simple risk control logic.

Three Core Goals For This Pipeline

  1. Precise Data Alignment All tick sorting uses the exchange native ts event timestamp provided by the API as the single source of truth. A sliding buffer window absorbs late, out-of-order ticks, generating clean, log-auditable aggregated bars including OHLC, VWAP, and tick density metrics.
  2. Optimized Network Resource Usage A single persistent WebSocket connection supports dynamic adding and removing of trading symbols. The connection never needs to be torn down and recreated, eliminating data gaps caused by reconnection cycles and drastically cutting local bandwidth and connection overhead.
  3. Accessible, Reusable Implementation The complete Python code works out of the box and follows standard WebSocket specs. Detailed inline comments and breakpoint-friendly logic make it suitable for student training and secondary development for personal quantitative strategies.

Core Concept: Dynamic Subscription Adjustment

Dynamic subscription adjustment means reusing one persistent WebSocket connection to update tracked instruments via the cmd_id=22004 command, which accepts lists of symbols to add or remove.

This approach avoids two inefficient workflows: repeatedly rebuilding connections and polling data via REST endpoints. The long-lived WebSocket stays active at all times, only sending incremental update instructions to sync symbol lists and cut redundant TCP handshake overhead.

Scenario Parameter Reference Table

Use Case Daily Development Pain Point API Parameter Configuration Local Verification Standard
Bulk subscribe gold & silver on startup Multiple initial connections consume local network limits cmd_id=22004, action=sub, code=["GOLD","SILVER"] Initial subscription logic runs once only; full symbol list prints to console logs
Add XAUUSD mid-operation Rebuilding connections creates gaps in tick data cmd_id=22004, action=add, code=["XAUUSD"] Local memory automatically deduplicates symbols; server returns ACK frame with no disconnect logs
Unsubscribe from SILVER mid-operation Full resubscription wastes bandwidth cmd_id=22004, action=del, code=["SILVER"] Target symbol removed from local set; no further SILVER ticks print to console
Duplicate subscription requests for the same asset Repeated market data floods the console cmd_id=22004, action=sub/add, code=["GOLD"] Local pre-check filters duplicate commands; no network request sent
Empty symbol list subscription command Malformed array triggers API runtime errors cmd_id=22004, action=sub/add/del, code=[] Local logic intercepts empty lists, prints warning and skips WebSocket transmission

Full Runnable Python Code (Local PC Compatible)

from collections import deque, set
import websocket
import json
import pandas as pd
import threading
import time

# Precious metal & forex universal WebSocket endpoint
WSS_URL = "wss://quote.alltick.co/quote-b-ws-api?token=YOUR_TOKEN"
# Fixed command ID for market subscription requests
SUBSCRIBE_CMD_ID = 22004
# 1200ms buffer window to absorb delayed, out-of-order ticks
BUFFER_WINDOW_MS = 1200
# Tick buffer with hard cap to limit local memory consumption
tick_buffer = deque(maxlen=8000)
# In-memory set to track active subscriptions and ghost subscriptions
subscriptions = set()
# Global WebSocket connection instance
ws_app = None

def send_subscription_action(action: str, code_list: list):
    """Send subscription updates over a single persistent TCP connection to cut local network waste"""
    global ws_app, subscriptions
    if not ws_app or not ws_app.sock.connected:
        print("[WARN] WebSocket inactive, skipping subscription command")
        return
    # Pre-filter empty symbol lists
    if not isinstance(code_list) or len(code_list) == 0:
        print("[WARN] Empty symbol list, discarding invalid command")
        return
    # Remove duplicate symbol entries
    unique_codes = list(set(code_list))
    payload = {
        "cmd_id": SUBSCRIBE_CMD_ID,
        "action": action,
        "code": unique_codes
    }
    ws_app.send(json.dumps(payload))
    # Sync local subscription state for log debugging
    if action == "sub" or action == "add":
        subscriptions.update(unique_codes)
    elif action == "del":
        for c in unique_codes:
            if c in subscriptions:
                subscriptions.remove(c)
    print(f"[SUB] action={action}, codes={unique_codes}, local_subs={subscriptions}")

def build_precision_bar(window_end_ts: int):
    """Generate multi-dimensional aggregated bars (OHLC + VWAP + tick density) aligned to exchange native timestamps"""
    global tick_buffer
    window_ticks = [tick for tick in tick_buffer if tick["ts"] <= window_end_ts]
    if len(window_ticks) == 0:
        return None
    df = pd.DataFrame(window_ticks)
    bar = {
        "window_end_ts": window_end_ts,
        "open": df["price"].iloc[0],
        "high": df["price"].max(),
        "low": df["price"].min(),
        "close": df["price"].iloc[-1],
        "tick_count": len(window_ticks),
        "vwap": df["price"].mean()
    }
    return bar

def on_open(ws):
    """Run bulk subscription once WebSocket handshake completes"""
    print("[INFO] Persistent WebSocket established, loading base instrument subscriptions")
    init_codes = ["GOLD", "SILVER", "XAUUSD"]
    send_subscription_action("sub", init_codes)

def on_message(ws, message):
    """Tick callback: filter invalid data and auto-expire old ticks to reduce RAM load"""
    global tick_buffer
    if not message:
        return
    try:
        msg = json.loads(message)
        code = msg.get("code", "")
        price = msg.get("price", 0)
        ts = msg.get("ts", 0)
        # Discard malformed empty/invalid tick entries
        if not code or price <= 0 or ts <= 0:
            return
        tick_buffer.append(msg)
        # Auto-purge ticks outside the defined buffer window
        current_ts = int(time.time() * 1000)
        expire_ts = current_ts - BUFFER_WINDOW_MS
        while tick_buffer and tick_buffer[0]["ts"] < expire_ts:
            tick_buffer.popleft()
    except Exception as e:
        print(f"[ERROR] Failed to parse tick payload: {str(e)}")

def on_error(ws, error):
    print(f"[ERROR] WebSocket connection fault: {error}")

def on_close(ws, close_code, close_msg):
    print(f"[INFO] Connection terminated | Code: {close_code}, Message: {close_msg}")
    # Clear cached data to avoid state corruption on reconnection
    subscriptions.clear()
    tick_buffer.clear()

def run_ws_client():
    global ws_app
    ws_app = websocket.WebSocketApp(
        WSS_URL,
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )
    # 10-second heartbeat to detect silent dead connections on unstable LAN/Wi-Fi
    ws_app.run_forever(ping_interval=10, ping_timeout=5)

# Background daemon thread for non-blocking WebSocket streaming
ws_thread = threading.Thread(target=run_ws_client, daemon=True)
ws_thread.start()

# Runtime example: add new metal symbol mid-execution
# send_subscription_action("add", ["XAGUSD"])
# Runtime example: cancel tracking for a symbol
# send_subscription_action("del", ["SILVER"])
Enter fullscreen mode Exit fullscreen mode

Common Runtime Bugs & Full Fixes

1. Uncontrolled Memory Growth From High-Frequency Ticks

Issue: During high-volatility events like NFP releases, thousands of ticks flood the buffer every second, pushing RAM usage to critical levels and freezing the program.
Detection: Log the length of tick_buffer continuously; trigger warning logs once the queue exceeds 5000 entries.
Resolution: Set a fixed max length for the deque buffer and purge expired ticks with every incoming payload. Offload candlestick aggregation to asynchronous logic to avoid blocking the main thread.

2. Silent Socket Disconnections Due To Network Instability

Issue: Fluctuating Wi-Fi or lab LAN connections sever the link silently without triggering the on_close callback. The program waits indefinitely for new ticks while stale data accumulates in memory.
Detection: Enable 10-second heartbeat checks; mark the connection as dead after two consecutive unresponded pings.
Resolution: Force-close and wipe all cached data on heartbeat timeout. You can extend the script with an auto-reconnect routine that reloads your full symbol list after re-establishing the link.

3. Ghost Subscriptions From Rapid Symbol Add/Remove Calls

Issue: Quickly toggling tracking on multiple instruments scrambles WebSocket command order, creating a mismatch between your local symbol set and the server’s active subscriptions. Some assets stop delivering tick data entirely.
Detection: Print your local subscription set with every update command and cross-reference against incoming tick symbol labels.
Resolution: Process subscription commands sequentially and filter duplicate requests locally to cut down unnecessary network traffic.

4. Silent Subscription Failures From Case-Sensitive Symbol Codes

Issue: Typing gold instead of standard GOLD sends a valid command with zero error output, yet no market data arrives, costing developers hours of debugging time.
Detection: Periodically cross-check symbols stored locally against incoming tick data and log mismatches as alerts.
Resolution: Maintain a constant list of official asset codes within the project, and validate all subscription inputs before transmitting WebSocket requests to catch invalid labels early.

Feature Scope Limitations

This pipeline only supports dynamic symbol add/remove operations within a single persistent WebSocket session.
It does not sync subscription states across multiple separate WebSocket connections, offer historical tick backfill endpoints, or accept custom commands outside of cmd_id=22004.

Wrap Up

Most developers building precious metal data pipelines get stuck on two core pain points: distorted candlesticks caused by unaligned tick timestamps, and wasted bandwidth/CPU from repeated WebSocket reconnections.

This lightweight Python implementation built for AllTick API solves both challenges in one unified workflow. It requires no cloud infrastructure and runs smoothly on standard consumer laptops. Whether you’re completing university quantitative coursework, building personal backtesting frameworks, or operating a lightweight local market data collector, you can drop this script directly into your project stack. It reduces local network and memory overhead while guaranteeing reproducible short-term market charts, letting you deploy a working real-time precious metal tick pipeline quickly.

Top comments (0)