Skip to main content
The demo above is what you’ll have by the end of this guide. It’s built with Apache ECharts and the NGN Market API’s format=ohlcv option, which returns data in exactly the shape charting libraries expect. Endpoint used:

Live demo

Hover over any candle to see the OHLCV breakdown. This demo runs on generated sample data so it works without an API key. The code below swaps that out for real NGX prices.

What the data looks like

When you request format=ohlcv, each entry in the data array is a compact array in this order:
[timestamp, open, high, low, close, volume]
{
  "data": [
    [1744243200000, 293.00, 297.50, 292.00, 295.00, 3821000],
    [1744329600000, 295.50, 301.00, 294.00, 300.00, 4102000],
    [1744416000000, 300.00, 303.50, 298.00, 302.50, 5143000]
  ]
}
The timestamp is in Unix milliseconds. ECharts works with milliseconds natively, so you can pass it straight to a Date constructor — no conversion needed. One thing to watch: ECharts candlestick series expects data in [open, close, lowest, highest] order, which is different from the API’s [open, high, low, close] order. The mapping section below handles this.

Vanilla JavaScript

You can load ECharts from a CDN with no build step required.
HTML
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<div id="chart" style="width: 100%; height: 400px;"></div>
JavaScript
const API_KEY = 'ngm_live_YOUR_KEY';

async function fetchOHLCV(symbol, period = '1y') {
  const res = await fetch(
    `https://api.ngnmarket.com/v1/companies/${symbol}/chart?period=${period}&format=ohlcv`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const json = await res.json();
  return json.data.data;
}

async function renderChart(symbol) {
  const raw = await fetchOHLCV(symbol);

  const dates = raw.map(([ts]) =>
    new Date(ts).toLocaleDateString('en-NG', { month: 'short', day: 'numeric' })
  );

  const chart = echarts.init(document.getElementById('chart'));

  chart.setOption({
    tooltip: {
      trigger: 'axis',
      axisPointer: { type: 'cross' },
    },
    grid: { left: 60, right: 20, top: 30, bottom: 60 },
    xAxis: {
      type: 'category',
      data: dates,
      splitLine: { show: false },
    },
    yAxis: {
      scale: true,
      splitLine: { lineStyle: { color: '#f0f0f0' } },
    },
    dataZoom: [
      { type: 'inside', start: 50, end: 100 },
      { type: 'slider', start: 50, end: 100 },
    ],
    series: [{
      type: 'candlestick',
      // API order: [ts, open, high, low, close] → ECharts order: [open, close, lowest, highest]
      data: raw.map(([_ts, open, high, low, close]) => [open, close, low, high]),
      itemStyle: {
        color: '#22c55e',
        color0: '#ef4444',
        borderColor: '#22c55e',
        borderColor0: '#ef4444',
      },
    }],
  });

  window.addEventListener('resize', () => chart.resize());
}

renderChart('DANGCEM');

React component

If you’re working in React, here’s a reusable component that handles fetching, rendering, and cleaning up the chart when the component unmounts. It also responds to container resizes automatically.
npm install echarts
React
import { useEffect, useRef } from 'react';
import * as echarts from 'echarts';

const API_KEY = 'ngm_live_YOUR_KEY';

async function fetchOHLCV(symbol, period = '1y') {
  const res = await fetch(
    `https://api.ngnmarket.com/v1/companies/${symbol}/chart?period=${period}&format=ohlcv`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const json = await res.json();
  return json.data.data;
}

export function CandlestickChart({ symbol = 'DANGCEM', period = '1y' }) {
  const containerRef = useRef(null);

  useEffect(() => {
    if (!containerRef.current) return;

    const chart = echarts.init(containerRef.current);

    fetchOHLCV(symbol, period).then(raw => {
      const dates = raw.map(([ts]) =>
        new Date(ts).toLocaleDateString('en-NG', { month: 'short', day: 'numeric' })
      );

      chart.setOption({
        tooltip: {
          trigger: 'axis',
          axisPointer: { type: 'cross' },
        },
        grid: { left: 60, right: 20, top: 30, bottom: 60 },
        xAxis: {
          type: 'category',
          data: dates,
          splitLine: { show: false },
        },
        yAxis: {
          scale: true,
          splitLine: { lineStyle: { color: '#f0f0f0' } },
        },
        dataZoom: [
          { type: 'inside', start: 50, end: 100 },
          { type: 'slider', start: 50, end: 100 },
        ],
        series: [{
          type: 'candlestick',
          data: raw.map(([_ts, open, high, low, close]) => [open, close, low, high]),
          itemStyle: {
            color: '#22c55e',
            color0: '#ef4444',
            borderColor: '#22c55e',
            borderColor0: '#ef4444',
          },
        }],
      });
    });

    const observer = new ResizeObserver(() => chart.resize());
    observer.observe(containerRef.current);

    return () => {
      observer.disconnect();
      chart.dispose();
    };
  }, [symbol, period]);

  return <div ref={containerRef} style={{ width: '100%', height: 400 }} />;
}
Drop it anywhere in your app:
React
<CandlestickChart symbol="GTCO" period="90d" />

Adding a volume panel

Volume is the sixth value in each array (index 5). ECharts supports multiple grids in a single chart instance, so you can stack a volume bar chart directly beneath the candlesticks and link their x-axes so zoom and pan stay in sync.
JavaScript
async function renderChartWithVolume(symbol) {
  const raw = await fetchOHLCV(symbol);

  const dates = raw.map(([ts]) =>
    new Date(ts).toLocaleDateString('en-NG', { month: 'short', day: 'numeric' })
  );

  const chart = echarts.init(document.getElementById('chart'));

  chart.setOption({
    tooltip: {
      trigger: 'axis',
      axisPointer: { type: 'cross' },
    },
    grid: [
      { left: 60, right: 20, top: 30, bottom: 120 },
      { left: 60, right: 20, top: '72%', bottom: 40 },
    ],
    xAxis: [
      { type: 'category', data: dates, splitLine: { show: false } },
      { type: 'category', gridIndex: 1, data: dates, show: false },
    ],
    yAxis: [
      { scale: true, splitLine: { lineStyle: { color: '#f0f0f0' } } },
      { gridIndex: 1, scale: true, axisLabel: { show: false }, splitLine: { show: false } },
    ],
    dataZoom: [
      { type: 'inside', xAxisIndex: [0, 1], start: 50, end: 100 },
      { type: 'slider', xAxisIndex: [0, 1], start: 50, end: 100 },
    ],
    series: [
      {
        type: 'candlestick',
        data: raw.map(([_ts, open, high, low, close]) => [open, close, low, high]),
        itemStyle: {
          color: '#22c55e',
          color0: '#ef4444',
          borderColor: '#22c55e',
          borderColor0: '#ef4444',
        },
      },
      {
        type: 'bar',
        xAxisIndex: 1,
        yAxisIndex: 1,
        data: raw.map(([_ts, open, _high, _low, close, volume]) => ({
          value: volume,
          itemStyle: { color: close >= open ? '#22c55e55' : '#ef444455' },
        })),
      },
    ],
  });

  window.addEventListener('resize', () => chart.resize());
}

A few things worth knowing

The data order mismatch. The API returns [timestamp, open, high, low, close, volume]. ECharts candlestick expects [open, close, lowest, highest]. Always map [open, close, low, high] — swapping high and low will render wicks upside down. Pick a period that fits your use case. 7d and 30d are good for a focused recent view. 1y works well for trend analysis. 5y is what you want for a long-term research screen. Shorter periods load faster and render more smoothly. The symbol lookup is case-insensitive. DANGCEM, dangcem, and DangCem all resolve to the same company. Switching symbols without rebuilding the chart. If you’re building a stock picker where users switch between companies, call chart.setOption({ series: [{ data: newCandles }] }) on the existing instance rather than disposing and reinitialising. The transition is smoother and you keep the zoom and scroll position.

Chart endpoint reference

All parameters for GET /companies/{symbol}/chart

Company profile reference

Pair this with company data for a full stock detail page