Skip to content

Custom Indicator

This example defines a Typical Price overlay:

text
(high + low + close) / 3

The definition parses user config, calculates one output per bar, draws the output as a line, and exposes a JSX component.

Code

tsx
import 'react-candlesticks/style.css';
import {
  Candlesticks,
  Chart,
  CustomLayerConfig,
  CustomLayerConfigComplete,
  LineConfig,
  LineConfigComplete,
  Panel,
  defineLayer,
  drawLineIndicator,
  exampleData,
  parseLineConfig,
} from 'react-candlesticks';

interface TypicalPriceConfig extends CustomLayerConfig {
  type: 'custom:typical-price';
  series?: {
    value?: false | LineConfig;
  };
}

interface TypicalPriceConfigComplete extends CustomLayerConfigComplete {
  type: 'custom:typical-price';
  series: {
    value: null | LineConfigComplete;
  };
}

const typicalPrice = defineLayer<
  TypicalPriceConfig,
  TypicalPriceConfigComplete
>({
  type: 'custom:typical-price',
  displayName: 'TypicalPrice',

  parseConfig: (config, _theme, panelId) => ({
    id: config.id ?? `typical-price_${panelId}`,
    type: 'custom:typical-price',
    indicator: true,
    defaultScale: {
      key: 'price_auto',
      domain: 'price',
      range: { type: 'auto' },
    },
    scale: config.scale ?? null,
    scalePolicy: 'derived',
    requiredInputKeys: ['high', 'low', 'close'],
    inputs: config.inputs ?? [
      { key: 'high', source: { type: 'price', field: 'high' } },
      { key: 'low', source: { type: 'price', field: 'low' } },
      { key: 'close', source: { type: 'price', field: 'close' } },
    ],
    outputs: ['value'],
    period: 1,
    offset: 0,
    lookback: 0,
    calculate: config.calculate ?? true,
    includeInAutoScale: config.includeInAutoScale ?? false,
    valueToY: (min, max, top, height) =>
      value => top + ((max - value) / (max - min)) * height,
    legend: null,
    yAxis: null,
    valueLabelFormatter:
      config.valueLabelFormatter ?? (value => value.toFixed(2)),
    series: {
      value: parseLineConfig(
        config.series?.value,
        undefined,
        '#a78bfa',
      ),
    },
  }),

  calculate: (_config, inputs, outputs, start, end) => {
    for (let index = start; index < end; index++) {
      outputs.value[index] = (
        inputs.high.values[index]
        + inputs.low.values[index]
        + inputs.close.values[index]
      ) / 3;
    }
  },

  draw: (
    context,
    axesContext,
    chartConfig,
    panelConfig,
    layerConfig,
    layout,
    viewportData,
    chartMetrics,
    panelMetrics,
    layerMetrics,
  ) => {
    drawLineIndicator(
      context,
      axesContext,
      chartConfig,
      panelConfig,
      layerConfig,
      layout,
      viewportData,
      chartMetrics,
      panelMetrics,
      layerMetrics,
      [{
        output: 'value',
        line: layerConfig.series.value,
        marker: null,
      }],
    );
  },
});

const TypicalPrice = typicalPrice.Component;

export default function App() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <Chart
        data={exampleData}
        granularity="d1"
        theme="dark"
        layerDefinitions={[typicalPrice]}
      >
        <Panel>
          <Candlesticks />
          <TypicalPrice
            series={{
              value: {
                color: '#a78bfa',
                width: 2,
                endDotSize: 4,
              },
            }}
          />
        </Panel>
      </Chart>
    </div>
  );
}

The indicator uses the same price_auto scale as the candlesticks, so its values are drawn in the same price coordinates. The definition must also be passed to layerDefinitions; rendering <TypicalPrice /> alone does not register its parser, calculator, or drawing function.

React Candlesticks is available on npm and GitHub under the MIT License.