Implementing Custom (line) Indicators with React Stockcharts Alpha

Tai Kersten
Proof of FinTech
6 min readOct 15, 2018

--

On AMP and Avocado we are looking to add in custom indicators for both ourselves and our users. After looking around Stack Overflow and Git we realized there are few clear human-readable examples describing an implementation of a custom line indicator. Our clients, built in react, also utilize Rrag’s awesome React Stockcharts library.

Although a very powerful library, React Stockcharts is young which means many of the interfaces and the source itself can be a bit of a hassle to implement. It is also very popular. Here we will be utilizing a similar pattern to the built-in indicator library included with the source that could be helpful to anyone looking to create an app with easy-ish to work on code.

A React Stockcharts indicator consists of the three following pieces:

  • The calculator which does the computation and reduction of data for visualization.
  • The indicator which implements the calculator and applies any options props passed down from the main Component outlined next.
  • The Component that renders the data into a canvas (in this case, a <LineSeries/> component)

One of the goals is to get as close as possible to just being able to implement indicators in the same fashion as other indicators. The reason for this is that React Stockcharts, in its alpha form, has an issue with how its component parts are laid out. Although there are many different types of ‘charts’ these charts are not layered in the source in a fully intuitive hierarchy and likewise it is hard to suss out which charts are better as primitives to do more complex drawing. This can lead to a severely messy component data flow and numerous hacks when implementing a React Stockchart component.

By implementing the indicator library ‘as-is’ and simply modify the source we can create code that can be easily implemented into your ChartCanvas as a prop into whatever you have chosen as your default indicator component (here we assume LineSeries is your default renderer). In this case, this is more a case of building a ‘mod’ for react-stockcharts than doing a full implementation of a component api given that there is no plug-and-play api for implementing custom indicators in react-stockcharts. Rather, you need to implement a new base component that acts as an entirely new chart overlay that is on the same component hierarchy as the primary chart.

Here, we are bringing forward the backend code and compartmentalizing it in such a way that describes its role as an extension of the base component (in this case, a CandlestickSeries component).

Some Beneficial Redundancy

Although it is redundant, I recommend moving the utils folder, baseIndicator.js, defaultOptionsForComputation.js, and defaultOptionsForAppearance.js files and folders into a seperate utils or component folder in your source location to help extend the lifespan of your code. These all can be found in the node_modules/react-stockcharts/lib folder in your installed react app.

For this overview I am using a custom directory structure like this:

> custom_indicators
> calculators
— defaultOptionsForComputation.js // src/lib/calculator
— index.js
— custom_indicator_calculator.js
> indicators
— baseIndicator.js // in src/lib/indicator
— index.js
— custom_indicator.js

The component is kept in the default source/component location of the rest of the platform’s components. Indexes contain standard index.js code exports for setting namespaces.

NOTE: The included code samples in this post have been changed for illustration and are not in their optimal state.

The Calculator

The calculator will be where the data processing of your indicator will occur. It returns a ‘calculator’ object that is used to compute the numbers for your line indicator. If you look at the source in react-stockcharts you will see that most indicators utilize a Sliding Window protocol which was popularized by TCP in the processing of large data streams.

//calculators/custom_indicator_calculator.jsimport { sum } from 'd3-array' // a basic Arr sum function from module_src
import { slidingWindow } from ‘../utils’
import { VWAP as defaultOptions } from ‘./defaultOptionsForComputation’/*************
MAIN FUNCTION
*************/
export default function() {
var options = defaultOptions
function calculator(data) {

/**************************************
/** Place your indicator logic here **/
/**************************************

}
calculator.undefinedLength = function() {
const { windowSize } = options
return windowSize — 1; // for 0-indexing purposes
};

// Options for computation

calculator.options = function(x) {
if (!arguments.length) {
return options;
}
options = { …defaultOptions, …x }
return calculator;
};
return calculator;
}

In the case of these samples the “windowSize” is basically saying, ‘how many datapoints do you want to process at once’. Thus if you are creating an RSI14 indicator you would set windowSize to 14. Below is a quick sample on how to use the windowSlider to handle multiple data points at once rather than using a single price point from the sourcePath option:

const { windowSize, sourcePath } = options;
// Sample VWAP indicator logic here
// sourcePath = [,”open”,”high”,”low”,”close”,”volume”]

var waverange = slidingWindow() //slidingWindow is a data processing protocol that allows you to easily process a large amount of data
.windowSize(windowSize)
.sourcePath(‘close’)
.accumulator(values => {
const cum_vol = sum(values.map(ohlcv => ohlcv.volume))
const cum_weighted_price = sum(
values.map(
ohlcv => ((ohlcv.low + ohlcv.close + ohlcv.high) / 3) * ohlcv.volume
)
);
return cum_weighted_price / cum_vol
});
return waverange(data)
A sample Volume Weighted Average Price (VWAP) integration for Avocado

The Indicator

The indicator implements the calculator and assembles all custom options. As-is, the main options you should implement are ‘windowSize’ and ‘stroke’.

//indicators/custom_indicator.js
import { rebind, merge } from ‘../utils’;
import { custom_indicator_calculator } from ‘../calculators’;
import baseIndicator from ‘./baseIndicator’;
const ALGORITHM_TYPE = ‘VWAP’;export default function() {
const base = baseIndicator()
.type(ALGORITHM_TYPE)
.accessor(d => d.vwap);
const underlyingAlgorithm = custom_indicator_calculator(); // calculator implementationconst mergedAlgorithm = merge()
.algorithm(underlyingAlgorithm)
.merge((datum, indicator) => {
datum.vwap = indicator;
});
const indicator = function(data, options = { merge: true }) {
if (options.merge) {
if (!base.accessor())
throw new Error(
`Set an accessor to ${ALGORITHM_TYPE} before calculating`
);
return mergedAlgorithm(data)
}
return underlyingAlgorithm(data)
};
rebind(indicator, base, ‘id’, ‘accessor’, ‘stroke’, ‘fill’, ‘echo’, ‘type’);
rebind(indicator, underlyingAlgorithm, ‘undefinedLength’)
rebind(indicator, underlyingAlgorithm, ‘options’)
rebind(indicator, mergedAlgorithm, ‘merge’, ‘skipUndefined’)
return indicator;
}

The Component

How you want to implement your component is entirely up to you. In the sample below we are utilizing our indicator over a CandleStick chart:


// components/Chart.js
<ChartCanvas
//…props_and_stuff
>

<CandlestickSeries
//… more_props_and_stuff
/>
<IndicatorTooltip
onClick={e => console.log(e)}
origin={[8 — margin.left, 8]}
textFill={theme.active}
labelFill={theme.text}
options={IndicatorOptions}
/>
<LineSeries
yAccessor={custom_indicator_state.accessor()}
stroke={custom_indicator.stroke()}
highlightOnHover
/>
//…
</ChartCanvas>

This is a chunk of the Avocado charting visualizer that has been flattened to create an easier data visualization environment due to the visualization handler of Avocado being extremely large. For a more conventional implementation, checkout AMP’s usage of both a UI framework and react-stockcharts indicators.

Optional Implementation Tips

We recommend putting the indicator components in their own module and assembling them with your preferred options as is shown in numerous code samples on the web.

//indicator_options.jsexport const vwap24 = vwap() // Disable when not hourly candles
.options({ windowSize: 10 }) //number of points considered
.stroke(‘#FFFF00’) // line color
.merge((d, c) => {
d.vwap = c;
})
.accessor(d => d.vwap)

Over time, we will be refactoring this code and post into an easy to implement set of interfaces in our public github until the codebase matures and this becomes a trivial post. Likewise, you can easily modify this code to work with more complex indicators requiring their own visual elements and components. Checkout our implementation of the AMP indicator components here for further implementation details.

Implement custom indicator components by combining the standard rsc library in clever ways.

--

--