Real‑Time Market Quotes with WebSockets + JavaScript (Part 1)
We’ll build a lightning‑fast quotes board that streams live bid/ask prices for FX, oil, gold, crypto, and the UK100 index — without hammering a REST API. The backend (Python + Flask‑SocketIO) connects to a market data WebSocket and pushes updates to the browser via Socket.IO. The frontend (HTML + JS) renders ticks in real time.
Series roadmap:
Part 1 (this post): Stand up the streaming backend and a minimal UI to show live quotes.
Part 2: Enrich the frontend — tick charts, spreads, connect/disconnect controls, and flashing price updates.
What you’ll build
A “Quotes Board” that:
- Subscribes to multiple symbols (FX, UKOIL, XAUUSD, Crypto, UK100).
- Streams low‑latency updates from a WebSocket feed.
- Broadcasts each tick to connected browsers via Socket.IO.
- Renders a simple table with bid/ask updating in place.
Project layout
/templates
├── index.html
app.py
.env
requirements.txtBackend setup
1) Install dependencies
pip install python-dotenv flask flask-socketio websocketsTo connect, please get a Websocket Key or sign up to request the Websocket API. (Mention this tutorial to get a free trial.)
2) Environment variables
Create a .env file in the project root:
TRADERMADE_API_KEY=your_websocket_api_key_hereWe’ll read this key at startup and use it to authenticate the WebSocket connection.
Building the streaming server
We’ll spin up a Flask app and wrap it with Socket.IO so the server can emit live ticks to all connected clients.
3) Imports & initialization
# app.py
import os
import json
import asyncio
import threadingimport websockets
from flask import Flask, render_template
from flask_socketio import SocketIO
from dotenv import load_dotenvload_dotenv()app = Flask(__name__, static_folder='static')
socketio = SocketIO(app, cors_allowed_origins="*")
4) Symbols & shared state
We’ll track the latest tick per symbol in memory and define the default watchlist (you can later make this configurable from the client).
latest_data = {}
symbols = [
'EURUSD', 'USDJPY', 'GBPUSD', 'US30USD',
'USDCAD', 'USDCHF', 'AUDUSD', 'OILUSD',
'EURCHF', 'EURJPY', 'UKOILUSD', 'UK100USD',
'XAUUSD', 'BTCUSD', 'EURNOK'
]5) WebSocket client (TraderMadeStream)
This class connects to the market data feed, subscribes to the requested symbols, handles reconnects, and forwards messages to the browser via Socket.IO.
class TraderMadeStream:
def __init__(self):
self.api_key = None
self.symbols = []
self.websocket = None
self.reconnect_attempts = 0
self.max_reconnect_attempts = 5
self.reconnect_delay = 3 # seconds
self.running = False# ---- configuration ----
def set_ws_key(self, api_key):
"""Set the TraderMade WebSocket API key"""
self.api_key = api_key
def set_symbols(self, symbols_string):
"""Set comma-separated symbols to subscribe to"""
self.symbols = [s.strip() for s in symbols_string.split(',') if s.strip()]# ---- streaming loop ----
async def connect_and_stream(self):
"""Connect to the WebSocket and stream ticks to Socket.IO"""
if not self.api_key:
raise ValueError("API Key not set. Use set_ws_key() first.")
if not self.symbols:
raise ValueError("No symbols set. Use set_symbols() first.")
self.running = True
while self.running:
try:
uri = f"wss://marketdata.tradermade.com/feedadv?api_key={self.api_key}"
async with websockets.connect(uri) as websocket:
self.websocket = websocket
self.reconnect_attempts = 0
print("WebSocket connection established")
# subscribe to symbols
subscribe_msg = {
"userKey": self.api_key,
"symbol": ",".join(self.symbols)
}
await websocket.send(json.dumps(subscribe_msg))
# process messages
while self.running:
message = await websocket.recv()
if not message.strip():
continue
if message.strip().lower() == "connected":
# initial confirmation message
continue
try:
data = json.loads(message)
except json.JSONDecodeError as e:
print("JSON decode error:", e, "message:", message[:120])
continue
self.process_message(data)
except websockets.exceptions.ConnectionClosed:
print("WebSocket connection closed")
await self.handle_reconnect()
except Exception as e:
print(f"WebSocket error: {e}")
await self.handle_reconnect()Create a single instance we can use in our routes and events:
async def handle_reconnect(self):
"""Backoff and retry on connection errors"""
if not self.running:
return
if self.reconnect_attempts < self.max_reconnect_attempts:
self.reconnect_attempts += 1
delay = self.reconnect_delay * self.reconnect_attempts
print(f"Reconnecting in {delay}s "
f"({self.reconnect_attempts}/{self.max_reconnect_attempts})...")
await asyncio.sleep(delay)
else:
print("Max reconnect attempts reached-check your network or API key.")
self.running = False
# ---- message handling ----
def process_message(self, data):
"""Store the latest tick and broadcast to clients"""
if isinstance(data, dict) and 'symbol' in data:
symbol = data['symbol']
latest_data[symbol] = data
socketio.emit('market_update', data)
# ---- lifecycle helpers ----
def disconnect(self):
self.running = False
print("Streaming stopped")
def start_streaming(self):
"""Run the async loop in a background thread so Flask can serve clients"""
def run():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self.connect_and_stream())
thread = threading.Thread(target=run, daemon=True)
thread.start()tm_stream = TraderMadeStream()6) Routes & Socket.IO events
- Serve
index.htmlat/. - When a browser connects, kick off streaming (if not already running) and send an initial snapshot so the table fills immediately.
- On disconnect, stop streaming.
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('connect')
def on_connect():
tm_stream.start_streaming()
if latest_data:
socketio.emit('initial_data', latest_data)
@socketio.on('disconnect')
def on_disconnect():
tm_stream.disconnect()7) Boot the server
Read the API key from .env, set the symbol list, and start Flask‑SocketIO.
if __name__ == "__main__":
API_KEY = os.getenv("TRADERMADE_API_KEY")
if not API_KEY:
print("Error: TRADERMADE_API_KEY not set")
raise SystemExit(1)
tm_stream.set_ws_key(API_KEY)
tm_stream.set_symbols(','.join(symbols))
socketio.run(
app,
host='0.0.0.0',
port=5000,
debug=True,
use_reloader=False
)Frontend: a minimal real‑time table
We’ll render a simple table and update (or insert) one row per symbol as ticks arrive.
8) HTML
/templates/index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Live Quotes Board</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 2rem; }
table { border-collapse: collapse; width: 100%; max-width: 720px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: right; }
th:first-child, td:first-child { text-align: left; }
thead { background: #f7f7f7; }
</style>
</head>
<body>
<h1>Real‑Time Market Quotes</h1>
<table id="rates">
<thead>
<tr><th>Symbol</th><th>Bid</th><th>Ask</th></tr>
</thead>
<tbody></tbody>
</table>
<script src="https://cdn.socket.io/4.5.1/socket.io.min.js"></script>
<script>
const socket = io();
const tbody = document.querySelector('#rates tbody');
// Insert or update a row for the symbol
const upsert = ({ symbol, bid, ask }) => {
if (!symbol) return;
let row = document.getElementById(symbol);
if (!row) {
row = tbody.insertRow();
row.id = symbol;
row.insertCell(); row.insertCell(); row.insertCell();
}
[symbol, bid, ask].forEach((v, i) => row.cells[i].textContent = v);
};
// Optional: hydrate the table with the latest known values
socket.on('initial_data', snap => Object.values(snap).forEach(upsert));
// Stream updates as they come in
socket.on('market_update', upsert);
</script>
</body>
</html>Run it
python app.pyOpen http://localhost:5000, and you should see symbols filling in with live bid/ask values as ticks stream in.
Get Rahul Khanna’s stories in your inbox
Join Medium for free to get updates from this writer.
Get the full code from GitHub.
Where we’re headed in Part 2
We’ll enhance the frontend to make the board feel like a trading terminal:
- Live tick micro‑charts
- Spread calculation & highlighting
- Connect/disconnect controls
- Flashing cells on price changes
Related reads
- Stream Data Using Python in 8 Lines of Code — A quick primer for streaming FX data.
- Python WebSocket Client — Real‑Time Forex — Build a simple Python client for a live FX feed.
- Your First Go WebSocket Client — Write a Go client for streaming forex data.
Notes & tips
- If you flush the server, the client will reconnect; the class includes simple backoff and a max retry guard.
- You can make the symbol list dynamic (e.g., via a Socket.IO event) and resubscribe on the fly.
- For production, consider: auth on the Socket.IO connection, rate limiting, observability (logs/metrics), and running behind a reverse proxy.
A Message from InsiderFinance
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the InsiderFinance Wire
- 📚 Take our FREE Masterclass
- 📈 Discover Powerful Trading Tools







