Lattice Grid Buy a licence

demo D261

Real-Time Trading Dashboard: One Market Feed in ESM

One market feed fans to a quotes grid and a movers grid narrowed by its own route, plus a price chart

createDataRouter · attach filter · priceFeed

This is a ready-made trading terminal: one market feed fans out to a quotes grid and a movers grid narrowed by its own route, with a price chart alongside. It shows a whole desk built from a single feed, ready to adapt to a real market source.

Building…
Loading a live grid…

This is the ES module version. The grid is imported straight from a CDN as a module, the same configuration as the vanilla tab without a global script tag, so it drops into any bundler or a bare module script.

The configuration

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.71.0/lattice-grid.min.css">

<div id="quotes" style="height: 300px"></div>
<div id="movers" style="height: 300px"></div>
<div id="chart" style="height: 220px"></div>

<script type="module">
  import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';
  import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
  import { MockWebSocket, priceFeed } from '@toclocoinc/lattice-grid/modules/mock-socket';

  const money = { style: 'currency', currency: 'USD', decimals: 2 };
  const upDown = [
    { id: 'up', when: { op: 'gt', value: 0 }, style: { color: '#3ddc97' } },
    { id: 'down', when: { op: 'lt', value: 0 }, style: { color: '#ff6b6b' } },
  ];
  const cols = [
    { field: 'symbol', title: 'Symbol', layout: { width: 84, pin: 'start' } },
    { field: 'last', title: 'Last', type: 'number', format: money },
    { field: 'chg', title: 'Chg', type: 'number', format: { decimals: 2 } },
    { field: 'bid', title: 'Bid', type: 'number', format: money },
    { field: 'ask', title: 'Ask', type: 'number', format: money },
  ];

  const quotes = createGrid(document.getElementById('quotes'), {
    rowKey: 'symbol', highlightOnChange: { duration: 350 }, formatting: { chg: upDown }, columns: cols,
  });
  const movers = createGrid(document.getElementById('movers'), {
    rowKey: 'symbol', highlightOnChange: { duration: 350 }, formatting: { chg: upDown }, columns: cols,
  });

  // overlap lets the one 'price' partition reach both grids. The movers route
  // reshapes its slice before it lands: a filter, then a sort, per route.
  const router = createDataRouter({ key: 'type', rowKey: 'symbol', overlap: true });
  router.attach(quotes, 'price');
  router.attach(movers, 'price', {
    filter: (row) => Math.abs(row.chg) >= 0.4,
    sort: { key: 'chg', dir: 'desc' },
  });

  createChart({ grid: quotes, container: document.getElementById('chart'), type: 'bar', x: 'symbol', y: 'last', title: 'Last price by symbol' });

  const socket = new MockWebSocket({ feed: priceFeed({ seed: 7 }), rate: 260 });
  socket.onmessage = (event) => {
    const message = JSON.parse(event.data);
    if (message.kind === 'snapshot') router.load(message.rows);
    else router.apply(message.changes);
  };
</script>