This tutorial walks you through building a live forex web dashboard that:
✅ Displays bid and ask prices
✅ Calculates and tracks spread (in pips and %)
✅ Visualizes either spread or bid/ask prices on a live chart
✅ Uses plain HTML + JavaScript + Highcharts + Socket.IO
🧩 1. Project Structure
project/
├── app.py
├── templates/
│ └── index.html
└── static/
├── script.js ← All JavaScript logic goes here
└── (style.css) ← Ignored in this tutorial
📄 2. index.html Overview
You already have the following components:
✅ Head Includes
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.js"></script>
<script src="static/script.js"></script>
✅ Body Layout
- Buttons: Connect / Disconnect / Theme toggle
- Dropdown: Chart mode selection
- Chart container:
<div id="priceChart">
- Table container:
<tbody id="tableBody">
⚙️ 3. JavaScript Logic in script.js
A. WebSocket Connection
Use Socket.IO to connect and listen for market_update:
let socket = null;
function startConnection() {
if (socket?.connected) return;
socket = io();
socket.on('connect', () => updateConnectionStatus('connected'));
socket.on('disconnect', () => updateConnectionStatus('disconnected'));
socket.on('market_update', (data) => {
updateTable(data);
if (data.symbol === selectedSymbol) updateChart(data);
});
socket.on('initial_data', (data) => {
for (const symbol in data) updateTable(data[symbol]);
resetChart();
});
}
document.getElementById('connectBtn').onclick = startConnection;
document.getElementById('disconnectBtn').onclick = stopConnection;
B. Highcharts Setup: Two Modes
Create Chart:
function createHighchart() {
const config = {
chart: { type: 'line' },
series: [],
tooltip: { shared: true },
time: { useUTC: false },
xAxis: { type: 'datetime' }
};
if (selectedChartType === 'spread') {
config.title = { text: `Spread (pips) – ${selectedSymbol}` };
config.series.push({ name: 'Spread (pips)', color: '#3a86ff', data: [] });
} else {
config.title = { text: `Bid & Ask – ${selectedSymbol}` };
config.series.push(
{ name: 'Bid', color: '#38b000', data: [] },
{ name: 'Ask', color: '#d90429', data: [] }
);
}
highchart = Highcharts.stockChart('priceChart', config);
}
Reset Chart on Symbol or Type Change:
function resetChart() {
chartSeriesData.length = 0;
if (highchart) highchart.destroy();
createHighchart();
}
C. Update Chart with New Tick Data
function updateChart(data) {
if (data.symbol !== selectedSymbol || !highchart) return;
const bid = parseFloat(data.bid);
const ask = parseFloat(data.ask);
const spreadPips = ((ask - bid) * pipFactor(bid)).toFixed(2);
const ts = Number(data.ts) || Date.now();
if (selectedChartType === 'spread') {
highchart.series[0].addPoint([ts, parseFloat(spreadPips)], true, false);
} else {
highchart.series[0].addPoint([ts, bid], true, false); // Bid
highchart.series[1].addPoint([ts, ask], true, false); // Ask
}
}
D. Build the Quote Table
Each update will: - Format bid/ask - Color cells based on direction - Show spread in pips and % - Track high/low spread
function updateTable(data) {
const rowId = 'row-' + data.symbol;
let row = document.getElementById(rowId);
if (!row) {
row = document.createElement('tr');
row.id = rowId;
row.onclick = () => {
selectedSymbol = data.symbol;
document.getElementById('chart-title').innerText = selectedSymbol;
resetChart();
};
document.getElementById('tableBody').appendChild(row);
}
const bid = parseFloat(data.bid);
const ask = parseFloat(data.ask);
const spread = ((ask - bid) * pipFactor(bid)).toFixed(2);
const spreadPercent = ((ask - bid) / bid * 100).toFixed(4);
row.innerHTML = `
<td>${data.symbol}</td>
<td>${bid.toFixed(5)}</td>
<td>${ask.toFixed(5)}</td>
<td>${spread}</td>
<td>${spreadPercent}%</td>
<td>–</td>
<td>–</td>
<td>${formatTime(data.ts)}</td>
`;
}
E. Utility Functions
function pipFactor(price) {
if (price < 5) return 10000;
if (price < 50) return 1000;
if (price < 500) return 100;
if (price < 5000) return 10;
return 1;
}
function formatTime(ts) {
let t = Number(ts);
if (t < 10000000000) t *= 1000;
const d = new Date(t);
return d.toLocaleTimeString('en-GB') + '.' + String(d.getMilliseconds()).padStart(3, '0');
}
F. Handle Chart Mode Switching
document.getElementById('chartTypeSelector').addEventListener('change', function () {
selectedChartType = this.value;
resetChart();
});
🚀 Final Result
Your dashboard now:
- ✅ Receives live price data
- ✅ Updates an interactive chart in real-time
- ✅ Lets users toggle between Spread or Bid & Ask
- ✅ Displays forex quote rows that auto-update on each tick
- ✅ Has a clean, responsive layout
python app.py
Now open https://localhost:5000 and voila!
Get the full code from Github.