Autochartist integration
This article will help you to integrate DXcharts with Autochartist in your application
Autochartist API
Check the Autochartist documentation for developers.
First, request API credentials. From the available APIs, select Technical Analysis API.
Technical Analysis API is used to get data about patterns.
Use Technical and statistical trade setups endpoint.
Fetching patterns from Autochartist
Use createMockChartDataProvider to create a mock data provider.
Note that we filter items that have:
- result_type -
ChartPattern - group_name -
stocksorforex(Optional if you use different data provider that has data for more symbols) - symbol - in the
SUPPORTED_SYMBOLSarray (Optional if you use different data provider that has data for more symbols - DXcharts pattern recognition demo supports limited set of symbols)
We also filter out duplicate symbols.
import React, { memo, useCallback, useEffect, useState } from 'react';import { Autochartist } from './autochartist-integration-example';import { from, Observable, of} from 'rxjs';import { catchError, map, startWith, switchMap } from 'rxjs/operators';import { RemoteData, remoteData } from '@dx-private/dxchart5-react/dist/utils/remote-data';import { buildErrorState, DEFAULT_ERROR_STATE, resetErrorState } from './error.utils';import { createMockChartDataProvider } from '@dx-private/dxchart5-react-mock-providers/dist';type AutochartistQuery = {pattern_types: string[];};type AutochartistItemData = {result_uid: number;result_type: string;group_name: string;symbol: string;analysis_text: string;};type Link = {rel: string;href: string;};export type AutochartistItem = {data: AutochartistItemData;links: Link[];};export type AutochartistResponse = {query: AutochartistQuery;items: AutochartistItem[];};const URL =`https://component.autochartist.com/to/resources/results?account_type=${ACCOUNT_TYPE}&broker_id=${BROKER_ID}&token=${AUTOCHARTIST_TOKEN}&expire=${AUTOCHARTIST_TOKEN_EXPIRE}&user=${USER}&locale=${LOCALE}&page_limit=1000`;const SUPPORTED_SYMBOLS = ['USDJPY', 'EURUSD', 'EURGBP ', 'USDCHF', 'GBPUSD', 'GOOGL', 'META', 'AAPL', 'PFE', 'MSFT', 'INTC', 'XOM'];const filterSymbols = (res: AutochartistResponse) => {return res.items.filter(item => {const { result_type, group_name, symbol } = item.data;const isChartPattern = result_type === 'ChartPattern';const isStock = group_name.toLowerCase() === 'stocks';const isForex = group_name.toLowerCase() === 'forex';const isSupportedSymbol = SUPPORTED_SYMBOLS.includes(symbol);return isSupportedSymbol && isChartPattern && (isStock || isForex);})}const filterUniqueSymbols = (items: AutochartistItem[]) => {const uniqueSymbols = new Set<string>();return items.filter(item => {const symbol = item.data.symbol;if (uniqueSymbols.has(symbol)) {return false;}uniqueSymbols.add(symbol);return true;})}const getSymbols = (): Observable<RemoteData<string, AutochartistItem[]>> => {return from(fetch(URL)).pipe(switchMap(res => res.json() as Promise<AutochartistResponse>),map(filterSymbols),map(filterUniqueSymbols),map(data => remoteData.success(data)),catchError(_error => of(remoteData.failure('Patterns not found'))),startWith(remoteData.pending),);};const dataProvider = createMockChartDataProvider();export const AutochartistIntegrationFetchItemsExample = memo(() => {const [chartPatternItems, setChartPatternItems] = useState<AutochartistItem[]>([]);const [isLoading, setLoading] = useState(true);const [error, setError] = useState(DEFAULT_ERROR_STATE);const handleError = useCallback((message: string) => {setError(buildErrorState(message));},[setError],);useEffect(() => {const subscription = getSymbols().subscribe(rd => {if (rd._tag !== 'RemotePending') {setLoading(false);}if (rd._tag === 'RemoteSuccess') {const data = rd.value;setChartPatternItems(data);}if (rd._tag === 'RemoteFailure') {handleError(rd.error);} else {setError(resetErrorState());}});return () => subscription.unsubscribe();}, []);if (isLoading) {return <div>Loading...</div>; // handle loading state}if (error.hasError) {return <div>{error.message}</div>; // handle error state}return <Autochartist chartPatternItems={chartPatternItems} chartDataProvider={dataProvider} />;});
There are also helper functions to handle error states in the examples:
export const DEFAULT_ERROR_STATE = { hasError: false, message: '' };export const buildErrorState = (message: string) => {return {message,hasError: true,};};export const resetErrorState = () => {return DEFAULT_ERROR_STATE;};
Fetching pattern drawing data from Autochartist
You can use include_detail and include_drawing to get the details and drawing data for the pattern in the previous example. However, it will increase the response data size and response time.
Another option is to use the getDrawingDataLink function to get the drawing data link for the pattern on demand. Then fetch the drawing data from the link which is Get pattern result drawing data endpoint.
import { ChartDataProvider } from '@dx-private/dxchart5-react/dist/providers/chart-data-provider';import React, { useCallback, useEffect, useMemo, useState } from 'react';import { AutochartistChart } from './autochartist-chart-lite-example';import { Sidebar } from './autochartist-sidebar-example';import { AutochartistItem } from './autochartist-integration-fetch-items-example';import { buildErrorState, DEFAULT_ERROR_STATE, resetErrorState } from './error.utils';import styled from 'styled-components';interface AutochartistProps {readonly chartPatternItems: AutochartistItem[];readonly chartDataProvider?: ChartDataProvider;}export type ChartPatternData = {result_uid: number;symbol: string;resistance_x0: string;resistance_x1: string;resistance_y0: number;resistance_y1: number;support_x0: string;support_x1: string;support_y0: number;support_y1: number;pattern_end_time: string;};export type ChartPatternDrawingDataLine = {x1: number; // timestamp of first pointx2: number; // timestamp of second pointy1: number; // price of first pointy2: number; // price of second pointname: string;};export type ChartPatternDrawingData = {symbol: string;direction: ForecastDirection;lines: ChartPatternDrawingDataLine[];arrow: ChartPatternDrawingDataLine[];forecast: ChartPatternDrawingDataLine[];predictionRectangle: ChartPatternDrawingDataLine[];interval: number;timeFrame: { from: number, to: number };timeZone: string;};export const FORECAST_DIRECTION = { bullish: 'BULLISH', bearish: 'BEARISH' } as const;export type ForecastDirection = typeof FORECAST_DIRECTION[keyof typeof FORECAST_DIRECTION];type ChartPatternUrlState = {drawingDataUrl: string;};type ChartPatternState = {drawingData: ChartPatternDrawingData;symbol: string;groupName: string;timeFrame: { from: number, to: number };};const DEFAULT_CHART_PATTERN_DRAWING_DATA: ChartPatternDrawingData = {symbol: '',interval: 60,timeFrame: { from: 0, to: 0 },timeZone: 'UTC',direction: FORECAST_DIRECTION.bullish,lines: [{ x1: 0, x2: 0, y1: 0, y2: 0, name: 'line' }],arrow: [{ x1: 0, x2: 0, y1: 0, y2: 0, name: 'arrow' }],forecast: [{ x1: 0, x2: 0, y1: 0, y2: 0, name: 'forecast' }],predictionRectangle: [{ x1: 0, x2: 0, y1: 0, y2: 0, name: 'predictionRectangle' }],};const DEFAULT_CHART_PATTERN_STATE: ChartPatternState = {drawingData: DEFAULT_CHART_PATTERN_DRAWING_DATA,symbol: '',groupName: '',timeFrame: { from: 0, to: 0 },};export const Autochartist = (props: AutochartistProps) => {const { chartPatternItems, chartDataProvider } = props;const firstPattern = chartPatternItems[0];const firstPatternDrawingDataUrl = useMemo(() => getDrawingDataLink(firstPattern), [firstPattern.data.result_uid]);const [currentItemIndex, setCurrentItemIndex] = useState(0);const [patternUrlState, setPatternUrlState] = useState<ChartPatternUrlState>({drawingDataUrl: firstPatternDrawingDataUrl,});const [patternState, setPatternState] = useState<ChartPatternState>(DEFAULT_CHART_PATTERN_STATE);const [error, setError] = useState(DEFAULT_ERROR_STATE);const onItemSelected = useCallback((item: AutochartistItem, index: number) => {const drawingDataURL = getDrawingDataLink(item);setPatternUrlState({drawingDataUrl: drawingDataURL,});setCurrentItemIndex(index);}, []);const getData = useCallback(async () => {try {const drawingDataRes = await fetch(patternUrlState.drawingDataUrl);const drawingData = (await drawingDataRes.json()) as ChartPatternDrawingData;const groupName = chartPatternItems[currentItemIndex].data.group_name;setPatternState({drawingData,symbol: drawingData.symbol,groupName,timeFrame: drawingData.timeFrame,});setError(resetErrorState());} catch (_error) {setError(buildErrorState('Failed to fetch pattern data'));}}, [patternUrlState.drawingDataUrl]);const showChart = useMemo(() => patternState.symbol && chartDataProvider && !error.hasError,[chartDataProvider, patternState.symbol, error.hasError],);useEffect(() => {getData();}, [getData]);return (<AutochartistWrapper><SidebarchartPatternsItems={chartPatternItems}selectedSymbol={patternState.symbol}onItemSelected={onItemSelected}/><ChartWrapper>{error.hasError && <div>{error.message}</div>} {/* handle error state */}{showChart && (<AutochartistChartsymbol={patternState.symbol}drawingData={patternState.drawingData}groupName={patternState.groupName}chartDataProvider={chartDataProvider}timeFrame={patternState.timeFrame}/>)}</ChartWrapper></AutochartistWrapper>);};export const AutochartistWrapper = styled.div`display: flex;overflow: hidden;height: 100%;`;export const ChartWrapper = styled.div`width: 100%;height: 100%;position: relative;`;const getDrawingDataLink = (item: AutochartistItem) => {return item.links.filter(link => link.rel === 'drawing-data')[0].href;};
Sidebar component is used to display the list of patterns. (Optional if you want to use your own component).
import React, { memo } from 'react';import styled from "styled-components";import { AutochartistItem } from './autochartist-integration-fetch-items-example';type SidebarProps = {chartPatternsItems: AutochartistItem[];selectedSymbol: string;onItemSelected: (item: AutochartistItem, index: number) => void;};export const Sidebar = memo<SidebarProps>(props => {const { chartPatternsItems, selectedSymbol, onItemSelected } = props;return (<SidebarStyled><TitleWrapperStyled><TitleStyled>Found Patterns</TitleStyled></TitleWrapperStyled><ListStyled>{chartPatternsItems.map((item, index) => {const { symbol, analysis_text } = item.data;const selectedClassName = selectedSymbol === symbol ? 'selected' : '';return (<ListItemStyledkey={symbol}className={selectedClassName}onClick={() => onItemSelected(item, index)}><ListItemTitleStyled>{symbol}</ListItemTitleStyled><ListItemDescriptionStyled>{analysis_text}</ListItemDescriptionStyled></ListItemStyled>);})}</ListStyled></SidebarStyled>);});// Some styled components for the sidebarconst SidebarStyled = styled.aside``;const TitleWrapperStyled = styled.div``;const TitleStyled = styled.h1``;const ListStyled = styled.ul``;const ListItemStyled = styled.li``;const ListItemTitleStyled = styled.h2``;const ListItemDescriptionStyled = styled.p``;
Displaying pattern as drawings on the chart
Note that since we integrate Autochartist with DXcharts Lite, we create getHistoryData wrapper that uses requestHistoryData function from ChartDataProvider but with additional timeout functionality.
DXcharts React do this by default but it requires implementing ChartDataProvider.
Timestamps received from Autochartist also need to be converted to milliseconds to be used on the chart.
Then use lines, forecast, predictionRectangle and arrow from the pattern drawing data to create drawings with the two helper functions drawingFromLine and toDrawing in the example below. Set drawings to the chart with chartInstance.drawings.setDrawings.
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';import styled from 'styled-components';import {ChartCandleData,ChartDataOptions,ChartDataProvider,} from '@dx-private/dxchart5-react/dist/providers/chart-data-provider';import { toCandles } from '@dx-private/dxchart5-react/dist/chart/model/chart.model';import { ChartPatternDrawingData, ChartPatternDrawingDataLine } from './autochartist-integration-example';import { createChart } from '@devexperts/dxcharts-lite';import { attachDrawingsComponent } from '@dx-private/dxchart5-modules/dist/drawings';import { ChartWithDrawings } from '@dx-private/dxchart5-modules/dist/drawings/drawings.config';import { AggregationPeriod } from '@dx-private/dxchart5-react/dist/chart/model/aggregation.model';import { RemoteData, remoteData } from '@dx-private/dxchart5-react/dist/utils/remote-data';import { asyncScheduler, from, of, Observable } from 'rxjs';import { catchError, map, observeOn, startWith, timeout } from 'rxjs/operators';import { Candle } from '@devexperts/dxcharts-lite/dist/chart/model/candle.model';import { DrawingType } from '@dx-private/dxchart5-modules/dist/drawings/model/drawing-types';import { Drawing } from '@dx-private/dxchart5-modules/dist/drawings/drawings.config';import { FORECAST_DIRECTION } from './autochartist-integration-example';import { buildErrorState, DEFAULT_ERROR_STATE, resetErrorState } from './error.utils';interface AutochartistChartProps {readonly symbol: string;readonly timeFrame: { from: number; to: number };readonly drawingData: ChartPatternDrawingData;readonly groupName: string;readonly chartDataProvider?: ChartDataProvider;}const RESISTANCE_LINE_COLOR = 'rgb(143,42,51)';const SUPPORT_LINE_COLOR = 'rgb(98,162,45)';const ARROW_LINE_COLOR = '#E4C31B';const FIVE_SECONDS_IN_MS = 5_000;const FIFTEEN_SECONDS_IN_MS = 15_000;const DEFAULT_DASH_CONFIG: number[] = [];const EXTEND_DASH_CONFIG = [3, 6];export const AutochartistChart = memo((props: AutochartistChartProps) => {const { symbol, drawingData, groupName, chartDataProvider, timeFrame } = props;const [chartInstance, setChartInstance] = useState<ChartWithDrawings | null>(null);const [isLoading, setLoading] = useState(true);const [error, setError] = useState(DEFAULT_ERROR_STATE);const chartSymbol = useMemo(() => groupName.toLowerCase() === 'forex' ? formatForex(symbol) : symbol, [groupName, symbol]);const handleError = useCallback((message: string) => {setError(buildErrorState(message));},[setError],);const initChart = useCallback((chartInstance: ChartWithDrawings, candles: Candle[]) => {if (!chartDataProvider) {return;}chartInstance.setChartType('candle');chartInstance.setData({candles,instrument: {symbol: chartSymbol,description: chartSymbol,priceIncrements: [0.0001],},});const { lines, forecast, predictionRectangle, arrow } = normalizeDrawingDataTimestamps(drawingData);const drawings = lines.map(line => {const color = line.name.toLowerCase().includes('resistance') ? RESISTANCE_LINE_COLOR : SUPPORT_LINE_COLOR;return drawingFromLine(line, 'line', color);});const isForecastPattern = forecast.length > 0;const isRectanglePattern = predictionRectangle.length > 0;if (isForecastPattern) {forecast.forEach((line) => {const color = line.name.toLowerCase().includes('resistance') ? RESISTANCE_LINE_COLOR : SUPPORT_LINE_COLOR;drawings.push(drawingFromLine(line, 'extended', color))})}if (isRectanglePattern) {const color = drawingData.direction === FORECAST_DIRECTION.bullish ? RESISTANCE_LINE_COLOR : SUPPORT_LINE_COLOR;predictionRectangle.forEach((line) => drawings.push(drawingFromLine(line, 'line', color)));}drawings.push(drawingFromLine(arrow, 'arrow', ARROW_LINE_COLOR));chartInstance.drawings.setDrawings({[chartSymbol]: drawings});const pattern = isForecastPattern ? forecast : predictionRectangle;const patternLines = [...lines, ...pattern];chartInstance.setAutoScale(false);const { xStart, xEnd } = toChartXScaleUnits(chartInstance, patternLines, arrow);const xSafeZoneMargin = createSafeZoneMarginPercent(xStart, xEnd, 10);chartInstance.scale.setXScale(xStart - xSafeZoneMargin, xEnd + xSafeZoneMargin);const { yStart, yEnd } = toChartYScaleUnits(patternLines, arrow);const ySafeZoneMargin = createSafeZoneMarginPercent(yStart, yEnd, 10);chartInstance.scale.setYScale(yStart - ySafeZoneMargin, yEnd + ySafeZoneMargin);},[chartSymbol, drawingData],);const getCandles = useCallback(() => {if (!chartDataProvider) {return;}const isForex = groupName.toLowerCase() === 'forex'const priceType = isForex ? 'mark' : 'last';const requestTimeout = isForex ? FIFTEEN_SECONDS_IN_MS : FIVE_SECONDS_IN_MS;return getHistoryData(chartDataProvider,chartSymbol,{ duration: drawingData.interval, durationType: 'm' },{ priceType, fromTime: timeFrame.from * 1000, toTime: timeFrame.to * 1000 }, // convert to millisecondsrequestTimeout,).pipe(observeOn(asyncScheduler),map(rd => {setLoading(rd._tag === 'RemotePending');if (rd._tag === 'RemoteSuccess') {const data = rd.value.map(toCandles);if (chartInstance) {initChart(chartInstance, data);}}if (rd._tag === 'RemoteFailure') {handleError(rd.error);} else {setError(resetErrorState());}}),).subscribe();}, [chartInstance, chartDataProvider, chartSymbol, getHistoryData]);const setRef = useCallback((node: HTMLDivElement) => {if (node && !chartInstance) {const chartConfig = { timezone: drawingData.timeZone, }setChartInstance(attachDrawingsComponent(createChart(node, chartConfig)));}}, []);useEffect(() => {const subscription = getCandles();return () => subscription?.unsubscribe();}, [getCandles]);if (isLoading) {return <div>Loading...</div>; // handle loading state}if (error.hasError) {return <div>{error.message}</div>; // handle error state}return <ChartContainerStyled ref={setRef} />;});const ChartContainerStyled = styled.div`width: 100%;height: 100%;`;const getHistoryData = (chartDataProvider: ChartDataProvider,symbol: string,aggregationPeriod: AggregationPeriod,options: {fromTime?: number;toTime?: number;} & ChartDataOptions,requestTimeout: number,): Observable<RemoteData<string, ChartCandleData[]>> => {return from(chartDataProvider.requestHistoryData(symbol, aggregationPeriod, options)).pipe(map(data => remoteData.success(data)),startWith(remoteData.pending),timeout(requestTimeout),catchError(_error => of(remoteData.failure('No data found for this symbol'))),);};const formatForex = (symbol: string) => {return symbol.slice(0, 3) + '/' + symbol.slice(3);};const drawingFromLine = (line: ChartPatternDrawingDataLine, type: DrawingType, color: string) => {return toDrawing(line.name, type, line.x1, line.y1, line.x2, line.y2, color);}const toDrawing = <T extends DrawingType>(id: string,type: T,x0: number,y0: number,x1: number,y1: number,color: string,): Drawing<T> => {const lineDash = type === 'extended' ? EXTEND_DASH_CONFIG : DEFAULT_DASH_CONFIG;const arrows =type === 'arrow'? {start: false,end: true,angle: 30,length: 10,lineColor: color,lineWidth: 2,lineDash: [],}: {};return {id,type,locked: true,keyPoints: [{timestamp: x0,value: y0,},{timestamp: x1,value: y1,},],properties: {line: {lineDash,lineColor: color,lineWidth: 2,},labels: {showPrice: false,showPriceChangeAbs: false,showPriceChangePercent: false,},arrows,measureBox: {showPriceChangePercent: false,showPriceChangeAbs: false,showBars: false,showTimeDiff: false,showDistance: false,showAngle: false,position: 'middle',textFormat: {bars: {singular: 'bar',plural: 'bars',},distance: {title: 'distance: ',pixels: 'px',},dateDiff: {minutes: { singular: 'minute', plural: 'minutes' },hours: { singular: 'hour', plural: 'hours' },days: { singular: 'day', plural: 'days' },},},},},_internalDrawing: {},};};const normalizeLinesTimestamps = (lines: ChartPatternDrawingDataLine[]) => {return lines.map((line) => ({ ...line, x1: line.x1 * 1000, x2: line.x2 * 1000 }));}const normalizeDrawingDataTimestamps = (drawingData: ChartPatternDrawingData) => {const lines = normalizeLinesTimestamps(drawingData.lines);const forecast = normalizeLinesTimestamps(drawingData.forecast);const predictionRectangle = normalizeLinesTimestamps(drawingData.predictionRectangle);const arrows = normalizeLinesTimestamps(drawingData.arrow);return { lines, forecast, predictionRectangle, arrow: arrows[0] };}const toChartXScaleUnits = (chartInstance: ChartWithDrawings,patternLines: ChartPatternDrawingDataLine[],arrow: ChartPatternDrawingDataLine,) => {const startCoords = patternLines.map((line) => line.x1);const startTimestamp = Math.min(...startCoords, arrow.x1,);const endCoords = patternLines.map((line) => line.x2);const endTimestamp = Math.max(...endCoords, arrow.x2,);const { startUnit } = chartInstance.chartModel.candleFromTimestamp(startTimestamp);const { endUnit } = chartInstance.chartModel.candleFromTimestamp(endTimestamp);return { xStart: startUnit, xEnd: endUnit };};const toChartYScaleUnits = (patternLines: ChartPatternDrawingDataLine[],arrow: ChartPatternDrawingDataLine,) => {const prices = patternLines.flatMap((line) => [line.y1, line.y2]);prices.push(arrow.y1, arrow.y2);const startPrice = Math.min(...prices,);const endPrice = Math.max(...prices);return { yStart: startPrice, yEnd: endPrice };};const createSafeZoneMarginPercent = (start: number, end: number, percent: number) => {const safeZoneMarginPercent = Math.abs(start - end) / 100;return percent * safeZoneMarginPercent;}
DXcharts Pattern Recognition demo
Check our Pattern Recognition demo here