Stock Candlestick Chart with Volume — Highcharts

A professional candlestick chart combining OHLC (open, high, low, close) price data with volume bars in a synchronized lower pane. The dual-pane layout presents price action in the main chart with corresponding trading volume below, sharing a common time axis. This format is the standard for technical analysis platforms, enabling traders to correlate price movements with trading activity and identify volume-confirmed trends or reversals.

Stock Candlestick Chart with Volume rendered with Highcharts

Renders

JavaScript source (Highcharts)

// anyplot.ai
// candlestick-volume: Stock Candlestick Chart with Volume
// Library: highcharts 12.6.0 | JavaScript 22.23.2
// Quality: 89/100 | Created: 2026-09-02

// The core bundle (no highcharts-more / modules/stock) has no "candlestick"
// series — that type ships in modules/stock.js, which isn't loaded. Each
// candle is built from an invisible "Floor" column stacked under a
// colorByPoint "Body" column for the open-close range, with per-direction
// "line" series (null-separated, one pair per candle) for the high-low wicks
// — pure core series types, no add-on module. The volume pane is a second
// yAxis (top/height split of the same plot area) sharing the chart's single
// xAxis, so the built-in crosshair and vertical gridlines span both panes
// automatically — no cross-pane sync code needed.
const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic mulberry32 PRNG) ------------------------
function mulberry32(seed) {
  return function () {
    seed |= 0;
    seed = (seed + 0x6d2b79f5) | 0;
    let z = Math.imul(seed ^ (seed >>> 15), 1 | seed);
    z = (z + Math.imul(z ^ (z >>> 7), 61 | z)) ^ z;
    return ((z ^ (z >>> 14)) >>> 0) / 4294967296;
  };
}
const rand = mulberry32(20240601);

const upColor = t.palette[0]; // #009E73 brand green — bullish (profit/up)
const downColor = t.palette[4]; // #AE3030 matte red — bearish (loss/down), finance semantic anchor

const dayMs = 24 * 3600 * 1000;
const numDays = 45; // continuous daily bars — crypto trades every calendar day
const startTime = Date.UTC(2024, 5, 1); // Sat 1 Jun 2024
const dates = Array.from({ length: numDays }, (_, i) => startTime + i * dayMs);

let price = 3200; // ETH-style token price in USD
const candles = dates.map((time) => {
  const open = price;
  const drift = (rand() - 0.5) * 90;
  const close = Math.max(50, open + drift);
  const swing = Math.abs(close - open) + rand() * 40 + 12;
  const high = Math.max(open, close) + rand() * swing * 0.4;
  const low = Math.max(10, Math.min(open, close) - rand() * swing * 0.4);
  const bullish = close >= open;
  const volume = Math.round(420000 + Math.abs(close - open) * 9000 + rand() * 160000);
  price = close;
  return {
    time,
    open: +open.toFixed(2),
    high: +high.toFixed(2),
    low: +low.toFixed(2),
    close: +close.toFixed(2),
    volume,
    bullish,
  };
});

const candleWidth = 9;

const floorData = candles.map((c) => ({ x: c.time, y: +Math.min(c.open, c.close).toFixed(2) }));
const bodyData = candles.map((c) => ({
  x: c.time,
  y: +Math.abs(c.close - c.open).toFixed(2),
  color: c.bullish ? upColor : downColor,
  custom: c,
}));
const wickUpData = candles
  .filter((c) => c.bullish)
  .flatMap((c) => [{ x: c.time, y: c.low }, { x: c.time, y: c.high }, { x: c.time, y: null }]);
const wickDownData = candles
  .filter((c) => !c.bullish)
  .flatMap((c) => [{ x: c.time, y: c.low }, { x: c.time, y: c.high }, { x: c.time, y: null }]);
const volumeData = candles.map((c) => ({ x: c.time, y: c.volume, color: c.bullish ? upColor : downColor }));

const allLows = candles.map((c) => c.low);
const allHighs = candles.map((c) => c.high);
const pricePad = (Math.max(...allHighs) - Math.min(...allLows)) * 0.08;
const priceMin = Math.floor(Math.min(...allLows) - pricePad);
const priceMax = Math.ceil(Math.max(...allHighs) + pricePad);

// --- Chart -------------------------------------------------------------------
Highcharts.chart("container", {
  chart: {
    type: "column",
    backgroundColor: "transparent",
    animation: false,
    style: { fontFamily: "inherit" },
  },
  credits: { enabled: false },
  colors: t.palette,
  title: {
    text: "candlestick-volume · javascript · highcharts · anyplot.ai",
    style: { color: t.ink, fontSize: "22px", fontWeight: "600" },
  },
  xAxis: {
    type: "datetime",
    lineColor: t.inkSoft,
    tickColor: t.inkSoft,
    gridLineColor: t.grid,
    gridLineWidth: 1,
    crosshair: { color: t.inkSoft, dashStyle: "Dash", width: 1 },
    labels: { style: { color: t.inkSoft, fontSize: "14px" } },
    title: { text: "Trading Date", style: { color: t.inkSoft, fontSize: "16px" } },
  },
  yAxis: [
    {
      // Price pane — top 68% of the plot area
      top: "0%",
      height: "68%",
      min: priceMin,
      max: priceMax,
      reversedStacks: false, // keep the invisible "Floor" series at the bottom of the stack
      title: { text: "Price (USD)", style: { color: t.inkSoft, fontSize: "16px" } },
      gridLineColor: t.grid,
      lineColor: t.inkSoft,
      labels: { style: { color: t.inkSoft, fontSize: "14px" } },
    },
    {
      // Volume pane — bottom 27%, a 5% gap separates it from the price pane
      top: "73%",
      height: "27%",
      offset: 0,
      min: 0,
      title: { text: "Volume", style: { color: t.inkSoft, fontSize: "16px" } },
      gridLineColor: t.grid,
      lineColor: t.inkSoft,
      labels: {
        style: { color: t.inkSoft, fontSize: "14px" },
        formatter() {
          return this.value >= 1000000
            ? Highcharts.numberFormat(this.value / 1000000, 1).replace(/\.0$/, "") + "M"
            : Highcharts.numberFormat(this.value / 1000, 0) + "k";
        },
      },
    },
  ],
  legend: {
    enabled: true,
    itemStyle: { color: t.inkSoft, fontSize: "14px" },
    itemHoverStyle: { color: t.ink },
    symbolRadius: 6,
    itemDistance: 24,
    padding: 12,
  },
  tooltip: {
    shared: true,
    outside: false,
    formatter: function () {
      const bodyPoint = this.points && this.points.find((p) => p.series.name === "Body");
      if (!bodyPoint) return false;
      const c = bodyPoint.point.custom;
      const volumePoint = this.points.find((p) => p.series.name === "Volume");
      let html =
        `<b>${Highcharts.dateFormat("%b %e, %Y", c.time)}</b><br/>` +
        `Open: ${c.open.toFixed(2)}<br/>High: ${c.high.toFixed(2)}<br/>` +
        `Low: ${c.low.toFixed(2)}<br/>Close: ${c.close.toFixed(2)}`;
      if (volumePoint) html += `<br/>Volume: ${Highcharts.numberFormat(volumePoint.y, 0)}`;
      return html;
    },
  },
  plotOptions: {
    series: { animation: false },
    column: { borderRadius: 0, animation: false },
  },
  series: [
    {
      name: "Floor",
      data: floorData,
      yAxis: 0,
      stacking: "normal",
      stack: "price",
      pointWidth: candleWidth,
      color: "transparent",
      borderWidth: 0,
      enableMouseTracking: false,
      showInLegend: false,
    },
    {
      type: "line",
      name: "Wicks (up)",
      data: wickUpData,
      yAxis: 0,
      color: upColor,
      lineWidth: 1.5,
      marker: { enabled: false },
      enableMouseTracking: false,
      showInLegend: false,
    },
    {
      type: "line",
      name: "Wicks (down)",
      data: wickDownData,
      yAxis: 0,
      color: downColor,
      lineWidth: 1.5,
      marker: { enabled: false },
      enableMouseTracking: false,
      showInLegend: false,
    },
    {
      name: "Body",
      data: bodyData,
      yAxis: 0,
      stacking: "normal",
      stack: "price",
      pointWidth: candleWidth,
      borderColor: t.pageBg,
      borderWidth: 1,
      showInLegend: false,
    },
    {
      name: "Volume",
      data: volumeData,
      yAxis: 1,
      pointWidth: candleWidth,
      borderWidth: 0,
      showInLegend: false,
    },
    {
      name: "Bullish (Close ≥ Open)",
      data: [],
      color: upColor,
      showInLegend: true,
    },
    {
      name: "Bearish (Close < Open)",
      data: [],
      color: downColor,
      showInLegend: true,
    },
  ],
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/candlestick-volume/highcharts/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "candlestick-volume",
  "language": "javascript",
  "library": "highcharts",
  "page": "https://anyplot.ai/candlestick-volume/javascript/highcharts",
  "hub": "https://anyplot.ai/candlestick-volume",
  "code_json": "https://api.anyplot.ai/specs/candlestick-volume/highcharts/code",
  "spec_json": "https://api.anyplot.ai/specs/candlestick-volume",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/candlestick-volume/javascript/highcharts/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/candlestick-volume/javascript/highcharts/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/candlestick-volume/javascript/highcharts/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/candlestick-volume/javascript/highcharts/plot-dark.html",
  "quality_score": 89.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Stock Candlestick Chart with Volume on anyplot.ai.

Other implementations