Real-Time Prediction Market Monitoring

Lesson 2 of 2

Building Live Market Dashboards with WebSocket Updates

Estimated time: 10 minutes

Building Live Market Dashboards with WebSocket Updates

Why WebSockets?

Polling has limitations:

  • Wasteful if data doesn't change
  • Maximum update frequency capped by polling interval
  • High latency (30+ seconds delay typical)

WebSockets and SSE enable true real-time updates with microsecond latency.

Option 1: Server-Sent Events (SSE)

SSE is simpler than WebSockets for one-directional server-to-client streaming.

const express = require('express');
const app = express();

app.get('/market-stream/:ticker', (req, res) => {
  const { ticker } = req.params;
  
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  const monitor = new MarketMonitor(5000);  // Poll every 5 seconds
  
  monitor.onMarketUpdate = (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  };
  
  monitor.watch(ticker);
  
  res.on('close', () => monitor.stop());
});

app.listen(3000, () => console.log('Streaming on :3000'));

Client-side:

const eventSource = new EventSource('/market-stream/KXSB');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  updateDashboard(data);
};

eventSource.onerror = (error) => {
  console.error('Stream error:', error);
  eventSource.close();
};

Option 2: WebSockets for Bi-Directional Communication

WebSockets allow client and server to send messages at any time.

const WebSocket = require('ws');
const http = require('http');
const express = require('express');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
  const subscriptions = new Set();
  
  ws.on('message', (message) => {
    const { action, ticker } = JSON.parse(message);
    
    if (action === 'subscribe') {
      subscriptions.add(ticker);
      ws.send(JSON.stringify({ type: 'subscribed', ticker }));
    } else if (action === 'unsubscribe') {
      subscriptions.delete(ticker);
    }
  });
  
  const monitor = new MarketMonitor(5000);
  
  monitor.onMarketUpdate = (ticker, data) => {
    if (subscriptions.has(ticker)) {
      ws.send(JSON.stringify({
        type: 'update',
        ticker,
        data
      }));
    }
  };
  
  // Monitor all subscribed tickers
  const checkInterval = setInterval(() => {
    for (const ticker of subscriptions) {
      monitor.watch(ticker);
    }
  }, 1000);
  
  ws.on('close', () => {
    clearInterval(checkInterval);
    monitor.stop();
  });
});

server.listen(3000);

Client-side:

const ws = new WebSocket('ws://localhost:3000');

ws.onopen = () => {
  ws.send(JSON.stringify({ action: 'subscribe', ticker: 'KXSB' }));
  ws.send(JSON.stringify({ action: 'subscribe', ticker: 'KXEL' }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  
  if (message.type === 'update') {
    updateMarketRow(message.ticker, message.data);
  }
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
  ws.close();
};

ws.onclose = () => {
  console.log('Disconnected. Reconnecting in 3 seconds...');
  setTimeout(() => location.reload(), 3000);
};

SSE vs WebSockets

FeatureSSEWebSocket
Browser SupportChrome/Firefox/SafariAll modern
Bi-directionalNo (server → client only)Yes
LatencyLowerLowest
ComplexitySimplerMore complex
Best forLive notificationsInteractive dashboards

Use SSE if: Broadcasting live market prices one-way to many clients. Use WebSockets if: Clients need to subscribe/unsubscribe dynamically.

Dashboard UI Example

<table id="markets">
  <thead>
    <tr>
      <th>Ticker</th>
      <th>Title</th>
      <th>Yes Price</th>
      <th>No Price</th>
      <th>Volume (24h)</th>
    </tr>
  </thead>
  <tbody id="market-rows"></tbody>
</table>

<script>
function updateMarketRow(ticker, markets) {
  let row = document.querySelector(`[data-ticker="${ticker}"]`);
  
  if (!row) {
    row = document.createElement('tr');
    row.setAttribute('data-ticker', ticker);
    document.querySelector('#market-rows').appendChild(row);
  }
  
  const m = markets[0];  // First market in response
  row.innerHTML = `
    <td>${ticker}</td>
    <td>${m.title}</td>
    <td>${(m.yes_price / 100).toFixed(2)}</td>
    <td>${(m.no_price / 100).toFixed(2)}</td>
    <td>${(m.volume_24h / 1e6).toFixed(1)}M</td>
  `;
}
</script>

Next Steps

Add persistence: log all price changes to a database, then build a chart showing price history and volatility over time.