Real-Time Streaming with WebSocket
Build a Python script that connects to the TickAtlas WebSocket endpoint and streams live EURUSD bid/ask prices in real-time. By the end of this guide, you will have a production-ready client with authentication, subscription management, and automatic reconnection.
Prerequisites
- TickAtlas API key on Starter plan or higher (get one here)
- Python 3.8+ installed
- websocket-client package (pip install websocket-client)
- Basic familiarity with JSON and WebSocket concepts
What You Will Build
A Python script that opens a persistent WebSocket connection to wss://tickatlas.com/ws/v1/quotes, authenticates with your API key, subscribes to forex symbols, and prints live M1 price data as it arrives. The script handles disconnections with exponential backoff and respects your daily credit quota. M1 means 1-minute candles. Each subscribed symbol sends a price update roughly every 60 seconds with bid, ask, spread, and full OHLCV data for the completed candle.
Step 1: Connect and Authenticate
Authentication happens over the WebSocket connection itself, not via URL parameters or HTTP headers. After the connection opens, you send a JSON message with your API key. The server must receive this within 10 seconds or it closes the connection.
import json
import websocket # pip install websocket-client
WS_URL = "wss://tickatlas.com/ws/v1/quotes"
API_KEY = "YOUR_API_KEY"
def on_open(ws):
"""Called when the connection is established."""
print("Connected. Sending auth...")
ws.send(json.dumps({
"action": "auth",
"key": API_KEY
}))
def on_message(ws, message):
msg = json.loads(message)
if msg["type"] == "authenticated":
print(f"Authenticated! Plan: {msg['plan']}")
print(f" Max symbols: {msg['max_symbols']}")
print(f" Credits per push: {msg['credits_per_push']}")
print(f" Daily quota remaining: {msg['daily_quota_remaining']}")
else:
print(f"Received: {msg}")
ws = websocket.WebSocketApp(WS_URL, on_open=on_open, on_message=on_message)
ws.run_forever() Step 2: Subscribe to Symbols
Once authenticated, tell the server which symbols you want. The subscribe message takes an array of symbol names. The server responds with a subscribed confirmation listing all active symbols.
def on_message(ws, message):
msg = json.loads(message)
if msg["type"] == "authenticated":
print(f"Authenticated on {msg['plan']} plan")
# Subscribe to symbols after auth succeeds
ws.send(json.dumps({
"action": "subscribe",
"symbols": ["EURUSD", "XAUUSD"]
}))
elif msg["type"] == "subscribed":
print(f"Now watching {msg['total_subscribed']} symbols: "
f"{msg['symbols']}")
elif msg["type"] == "error":
print(f"Error [{msg['code']}]: {msg['message']}") Step 3: Handle Incoming Messages
The server sends several message types. The most important is quote which carries the actual price data.
def on_message(ws, message):
msg = json.loads(message)
if msg["type"] == "authenticated":
ws.send(json.dumps({
"action": "subscribe",
"symbols": ["EURUSD", "XAUUSD"]
}))
elif msg["type"] == "subscribed":
print(f"Watching {msg['total_subscribed']} symbols")
elif msg["type"] == "quote":
q = msg["data"]
print(f"[{q['time']}] {q['symbol']} "
f"O={q['open']} H={q['high']} L={q['low']} C={q['close']} "
f"bid={q['bid']} ask={q['ask']} spread={q['spread']} "
f"vol={q['volume']}")
elif msg["type"] == "heartbeat":
# Server sends this every 30 seconds — connection is alive
pass
elif msg["type"] == "pong":
# Response to our ping — useful for latency measurement
pass
elif msg["type"] == "error":
code = msg["code"]
print(f"Error [{code}]: {msg['message']}")
if code == "quota_exceeded":
print("Daily quota exhausted. Quotes paused until midnight UTC.")
elif code in ("auth_timeout", "invalid_key", "connection_limit"):
print("Fatal error — connection will close.") Step 4: Handle Reconnection
WebSocket connections can drop due to network issues, server restarts, or maintenance windows. A production client must reconnect automatically. Use exponential backoff to avoid hammering the server:
import time
def connect_with_backoff():
"""
Reconnect with exponential backoff.
Starts at 1s, doubles each attempt, caps at 60s.
Resets to 1s after a successful connection.
"""
delay = 1
while True:
try:
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close,
)
# ping_interval keeps the TCP connection alive
ws.run_forever(ping_interval=30, ping_timeout=10)
except KeyboardInterrupt:
print("Shutting down.")
break
except Exception as e:
print(f"Connection error: {e}")
print(f"Reconnecting in {delay}s...")
time.sleep(delay)
delay = min(delay * 2, 60)
def on_error(ws, error):
print(f"WebSocket error: {error}")
def on_close(ws, close_status_code, close_msg):
print(f"Connection closed ({close_status_code}): {close_msg}") Complete Example
Here is the full script combining all four steps into a production-ready client. Save this as ws_stream.py and run it:
pip install websocket-client
export TICKATLAS_API_KEY="your_key_here"
python ws_stream.py #!/usr/bin/env python3
"""
TickAtlas WebSocket Streaming Client
Streams real-time M1 bid/ask data for subscribed symbols.
"""
import json
import time
import os
import websocket # pip install websocket-client
WS_URL = "wss://tickatlas.com/ws/v1/quotes"
API_KEY = os.environ.get("TICKATLAS_API_KEY", "YOUR_API_KEY")
SYMBOLS = ["EURUSD", "XAUUSD", "GBPUSD"]
reconnect_delay = 1
def on_open(ws):
global reconnect_delay
reconnect_delay = 1 # Reset backoff on successful connect
print("Connected. Authenticating...")
ws.send(json.dumps({
"action": "auth",
"key": API_KEY
}))
def on_message(ws, message):
msg = json.loads(message)
msg_type = msg.get("type")
if msg_type == "authenticated":
print(f"Authenticated | plan={msg['plan']} "
f"quota={msg['daily_quota_remaining']}")
ws.send(json.dumps({
"action": "subscribe",
"symbols": SYMBOLS
}))
elif msg_type == "subscribed":
print(f"Subscribed to {msg['total_subscribed']} symbols: "
f"{msg['symbols']}")
elif msg_type == "quote":
q = msg["data"]
print(f" {q['symbol']:8s} bid={q['bid']:<10} ask={q['ask']:<10} "
f"spread={q['spread']:<6} vol={q['volume']}")
elif msg_type == "heartbeat":
pass # Connection alive
elif msg_type == "error":
print(f"ERROR [{msg['code']}]: {msg['message']}")
if msg["code"] == "quota_exceeded":
print("Quota exhausted — quotes paused until midnight UTC.")
def on_error(ws, error):
print(f"WebSocket error: {error}")
def on_close(ws, close_status_code, close_msg):
print(f"Disconnected ({close_status_code}): {close_msg}")
def main():
global reconnect_delay
print(f"Connecting to {WS_URL}")
print(f"Symbols: {SYMBOLS}")
print("-" * 60)
while True:
try:
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close,
)
ws.run_forever(ping_interval=30, ping_timeout=10)
except KeyboardInterrupt:
print("\nShutting down.")
break
except Exception as e:
print(f"Connection failed: {e}")
print(f"Reconnecting in {reconnect_delay}s...")
time.sleep(reconnect_delay)
reconnect_delay = min(reconnect_delay * 2, 60)
if __name__ == "__main__":
main() JavaScript / Browser Example
If you are building a web dashboard or using Node.js, here is the equivalent client in JavaScript:
// Browser / Node.js WebSocket streaming client
// Node.js: npm install ws, then const WebSocket = require("ws");
const WS_URL = "wss://tickatlas.com/ws/v1/quotes";
const API_KEY = "YOUR_API_KEY";
const SYMBOLS = ["EURUSD", "XAUUSD"];
let reconnectDelay = 1000;
function connect() {
const ws = new WebSocket(WS_URL);
ws.onopen = () => {
reconnectDelay = 1000;
console.log("Connected. Authenticating...");
ws.send(JSON.stringify({ action: "auth", api_key: API_KEY }));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "authenticated":
console.log("Auth OK. Plan:", msg.plan,
"Quota:", msg.daily_quota_remaining);
ws.send(JSON.stringify({
action: "subscribe",
symbols: SYMBOLS
}));
break;
case "subscribed":
console.log("Subscribed to", msg.symbols);
break;
case "quote":
const q = msg.data;
console.log(
q.symbol, "bid=" + q.bid, "ask=" + q.ask,
"spread=" + q.spread
);
// Update your UI or trading logic here
break;
case "heartbeat":
break;
case "error":
console.error("[" + msg.code + "]", msg.message);
break;
}
};
ws.onclose = (event) => {
console.log("Disconnected (" + event.code + "). "
+ "Reconnecting in " + reconnectDelay + "ms...");
setTimeout(connect, reconnectDelay);
reconnectDelay = Math.min(reconnectDelay * 2, 60000);
};
ws.onerror = (error) => {
console.error("WS error:", error.message || error);
};
}
connect(); Understanding Credits
| Plan | Credits per Push | 5 Symbols / Day | 20 Symbols / Day |
|---|---|---|---|
| Starter ($29/mo) | 5 | ~36,000 credits | N/A (5 symbol limit) |
| Pro ($79/mo) | 4 | ~28,800 credits | ~115,200 credits |
| Enterprise ($349/mo) | 3 | ~21,600 credits | ~86,400 credits |
Troubleshooting Common Errors
auth_timeout
Cause: You did not send the auth message within 10 seconds of connecting. Fix: Send the auth message immediately in your on_open handler. Check that your connection is not blocked by a proxy or firewall that delays the initial send.
quota_exceeded
Cause: Your daily credit quota is exhausted. Fix: Reduce the number of subscribed symbols, upgrade to a higher plan for a larger quota, or wait for midnight UTC reset. The connection stays open — do not disconnect and reconnect.
connection_limit
Cause: You already have 2 active WebSocket connections for this API key. Fix: Close one of the existing connections before opening a new one. If you believe a stale connection is still counted, wait a few minutes for the server to detect the stale connection and release the slot.
forbidden
Cause: Your API key is on the Free or Pay-As-You-Go plan, which does not include WebSocket access. Fix: Upgrade to the Starter plan ($29/mo) or higher.
invalid_key
Cause: The API key does not exist or has been revoked. Fix: Check your key in the dashboard. If it was rotated, update your script with the new key.
Related
What's Next
- API Reference
WebSocket API Reference — full message schema and error codes
Read more - Link
Compare plans and choose the right one for your symbol count
Read more - Guide
How to Build a Trading Bot
Step-by-step Python bot with RSI + MACD strategy
Read more - Guide
Build a Trading Dashboard
Real-time dashboard with charts and indicators
Read more