We recently helped a crypto exchange extend its product offering into precious metals. The goal was to let traders view and analyze XAU/USD and XAG/USD side-by-side with their digital-asset portfolios — all through a single, high-performance interface. Achieving that required us to rethink how we source, normalize, and serve commodity market data. In this post, we share the architecture decisions and code patterns that got us there, focusing on the precious metals API layer.
Understanding the real requirement
Our client’s users needed more than a static gold price. They expected a live ticker, interactive candlestick charts, spread monitoring, and the ability to backtest cross-asset strategies. Translating that into engineering terms gave us a clear set of non-negotiables: a streaming feed with sub-100ms latency, consistent data structures across all instruments, and a history store that could be queried without rate-limiting the live connection.
Common pitfalls when adopting a precious metals data feed
We started by surveying publicly available REST endpoints, and the issues were immediate. Response formats varied wildly — one endpoint might return {“last”: 2385.5} while another gave {“bid”: 2385.4, “ask”: 2385.6} and yet another wrapped everything in a proprietary envelope. Merging those streams created a maintenance nightmare. Time handling was even worse: mixing local time zones with UTC caused our hourly candles to drift by several minutes over a trading week, breaking any signal that relied on precise period boundaries. We also quickly hit the limits of polling — when gold started moving fast, the UI displayed stroboscopic jumps instead of smooth price action.
Building on a stable streaming foundation
The fix was to standardise on WebSocket ingestion from a provider that natively supports low-latency precious metals data. AllTick, for instance, gave us a single persistent connection that pushed tick-level updates with all the fields we needed: symbol, price, volume, and a reliable UTC timestamp. Every message was normalised at the edge into a common JSON schema before entering our message bus:
{
"symbol": "XAUUSD",
"price": "2385.50",
"volume": "10",
"timestamp": "2026-07-31T09:30:00Z"
}
Here is the minimal Python listener we used to benchmark the feed’s performance and verify data integrity:
import websocket
import json
def on_message(ws, message):
data = json.loads(message)
symbol = data.get("symbol")
price = data.get("price")
timestamp = data.get("timestamp")
print(
f"{symbol} Price: {price} Time: {timestamp}"
)
def on_open(ws):
subscribe_message = {
"action": "subscribe",
"symbol": "XAUUSD",
"type": "tick"
}
ws.send(json.dumps(subscribe_message))
ws = websocket.WebSocketApp(
"wss://api.alltick.co/ws",
on_open=on_open,
on_message=on_message
)
ws.run_forever()
With this setup, we achieved consistent end-to-end latency under 80ms, matching the responsiveness of the native crypto feeds.
From raw stream to production analytics
Once the tick stream was stable, we layered on the analytics that traders actually interact with:
| Data Type | Application Scenario |
|---|---|
| Real-time price | Quote display, price alerts |
| Tick data | High-frequency analysis, monitoring |
| K-line data | Trend analysis, indicator calculation |
| Bid/ask quotes | Spread analysis |
| Timestamp | Data sorting, period conversion |
We built a lightweight aggregation service that converts ticks into 1-min, 5-min, and daily K-lines, calculating open, high, low, and close purely from UTC-sorted windows. Historical data was periodically exported to Parquet files and stored in object storage, allowing strategy backtesting to run entirely offline. Finally, we added guardrails — duplicate timestamps are dropped, and price spikes beyond a configurable threshold are quarantined before they distort technical indicators.
If you are thinking about adding gold or silver to your trading application, start with a solid, streaming-first precious metals API and invest the effort upfront in data normalisation. The rest of the stack will thank you.

Top comments (0)