Custom Indicator
This example defines a Typical Price overlay:
text
(high + low + close) / 3The 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.