{"spec_id":"candlestick-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// candlestick-basic: Basic Candlestick Chart\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-08-24\n\n// The core bundle (no highcharts-more / modules/stock) has no \"candlestick\"\n// series — that type ships in modules/stock.js, which isn't loaded. Each\n// candle is built the same way box-basic builds boxplots: an invisible\n// \"Floor\" column stacked under a colorByPoint \"Body\" column for the\n// open→close range, with per-direction \"line\" series (null-separated, one\n// pair per candle) for the high→low wicks drawn *before* the body so the\n// wick's middle segment is covered by the box — pure core series types, no\n// add-on module, no cross-library workaround.\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\nlet seed = 42;\nfunction lcg() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\n\nconst upColor = t.palette[0]; // #009E73 brand green — bullish (profit/up)\nconst downColor = t.palette[4]; // #AE3030 matte red — bearish (loss/down), finance semantic anchor\n\nconst dayMs = 24 * 3600 * 1000;\nconst numDays = 30;\nconst dates = [];\nlet cursor = Date.UTC(2024, 0, 2); // Tue 2 Jan 2024\nwhile (dates.length < numDays) {\n  const weekday = new Date(cursor).getUTCDay();\n  if (weekday !== 0 && weekday !== 6) dates.push(cursor);\n  cursor += dayMs;\n}\n\nlet price = 128;\nconst candles = dates.map((time) => {\n  const open = price;\n  const drift = (lcg() - 0.48) * 4.2;\n  const close = Math.max(5, open + drift);\n  const high = Math.max(open, close) + lcg() * 2.4;\n  const low = Math.max(1, Math.min(open, close) - lcg() * 2.4);\n  price = close;\n  return {\n    time,\n    open: +open.toFixed(2),\n    high: +high.toFixed(2),\n    low: +low.toFixed(2),\n    close: +close.toFixed(2),\n    bullish: close >= open,\n  };\n});\n\nconst floorData = candles.map((c) => ({\n  x: c.time,\n  y: +Math.min(c.open, c.close).toFixed(2),\n}));\nconst bodyData = candles.map((c) => ({\n  x: c.time,\n  y: +Math.abs(c.close - c.open).toFixed(2),\n  color: c.bullish ? upColor : downColor,\n  custom: c,\n}));\nconst wickUpData = candles\n  .filter((c) => c.bullish)\n  .flatMap((c) => [{ x: c.time, y: c.low }, { x: c.time, y: c.high }, { x: c.time, y: null }]);\nconst wickDownData = candles\n  .filter((c) => !c.bullish)\n  .flatMap((c) => [{ x: c.time, y: c.low }, { x: c.time, y: c.high }, { x: c.time, y: null }]);\n\nconst allLows = candles.map((c) => c.low);\nconst allHighs = candles.map((c) => c.high);\nconst pad = (Math.max(...allHighs) - Math.min(...allLows)) * 0.08;\nconst yMin = Math.floor(Math.min(...allLows) - pad);\nconst yMax = Math.ceil(Math.max(...allHighs) + pad);\n\n// --- Focal-point annotations (derived from the data, not hard-coded) --------\n// Largest single-day move, by |close - open|.\nlet moveIdx = 0;\ncandles.forEach((c, i) => {\n  const move = Math.abs(c.close - c.open);\n  const bestMove = Math.abs(candles[moveIdx].close - candles[moveIdx].open);\n  if (move > bestMove) moveIdx = i;\n});\nconst moveCandle = candles[moveIdx];\nconst moveSize = Math.abs(moveCandle.close - moveCandle.open);\n\n// Largest peak-to-trough pullback (max drawdown on closing price).\nlet peakIdx = 0;\nlet drawdownPeakIdx = 0;\nlet drawdownTroughIdx = 0;\nlet maxDrawdown = 0;\ncandles.forEach((c, i) => {\n  if (c.close > candles[peakIdx].close) peakIdx = i;\n  const drawdown = candles[peakIdx].close - c.close;\n  if (drawdown > maxDrawdown) {\n    maxDrawdown = drawdown;\n    drawdownPeakIdx = peakIdx;\n    drawdownTroughIdx = i;\n  }\n});\nconst pullbackFrom = candles[drawdownPeakIdx].time - dayMs / 2;\nconst pullbackTo = candles[drawdownTroughIdx].time + dayMs / 2;\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"column\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    plotBorderWidth: 1,\n    plotBorderColor: t.grid,\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"candlestick-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text:\n      `Shaded band: largest pullback (-$${maxDrawdown.toFixed(2)}) · ` +\n      `Dashed line: largest single-day move (${moveCandle.bullish ? \"+\" : \"-\"}$${moveSize.toFixed(2)} on ${Highcharts.dateFormat(\"%b %e\", moveCandle.time)})`,\n    style: { color: t.inkSoft, fontSize: \"13px\" },\n  },\n  xAxis: {\n    type: \"datetime\",\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    title: { text: \"Trading Date\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    plotBands: [\n      {\n        from: pullbackFrom,\n        to: pullbackTo,\n        color: Highcharts.color(downColor).setOpacity(0.08).get(),\n        zIndex: 0,\n      },\n    ],\n    plotLines: [\n      {\n        value: moveCandle.time,\n        color: t.inkSoft,\n        dashStyle: \"Dash\",\n        width: 1.5,\n        zIndex: 3,\n      },\n    ],\n  },\n  yAxis: {\n    min: yMin,\n    max: yMax,\n    reversedStacks: false, // keep series[0] (invisible floor) at the bottom of the stack\n    title: { text: \"Share Price (USD)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    enabled: true,\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n    symbolRadius: 6,\n    itemDistance: 24,\n    padding: 12,\n  },\n  tooltip: {\n    outside: false,\n    formatter: function () {\n      const c = this.point.custom;\n      if (!c) return false;\n      return (\n        `<b>${Highcharts.dateFormat(\"%b %e, %Y\", c.time)}</b><br/>` +\n        `Open: ${c.open.toFixed(2)}<br/>High: ${c.high.toFixed(2)}<br/>` +\n        `Low: ${c.low.toFixed(2)}<br/>Close: ${c.close.toFixed(2)}`\n      );\n    },\n  },\n  plotOptions: {\n    column: {\n      stacking: \"normal\",\n      pointWidth: 12,\n      borderRadius: 0,\n      animation: false,\n    },\n    series: { animation: false },\n  },\n  series: [\n    {\n      name: \"Floor\",\n      data: floorData,\n      color: \"transparent\",\n      borderWidth: 0,\n      enableMouseTracking: false,\n      showInLegend: false,\n    },\n    {\n      type: \"line\",\n      name: \"Wicks (up)\",\n      data: wickUpData,\n      color: upColor,\n      lineWidth: 1.5,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      showInLegend: false,\n    },\n    {\n      type: \"line\",\n      name: \"Wicks (down)\",\n      data: wickDownData,\n      color: downColor,\n      lineWidth: 1.5,\n      marker: { enabled: false },\n      enableMouseTracking: false,\n      showInLegend: false,\n    },\n    {\n      name: \"Body\",\n      data: bodyData,\n      borderColor: t.pageBg,\n      borderWidth: 1,\n      showInLegend: false,\n    },\n    {\n      name: \"Bullish (Close ≥ Open)\",\n      type: \"column\",\n      data: [],\n      color: upColor,\n      showInLegend: true,\n    },\n    {\n      name: \"Bearish (Close < Open)\",\n      type: \"column\",\n      data: [],\n      color: downColor,\n      showInLegend: true,\n    },\n  ],\n});\n"}