Chart Data Provider
ChartDataProvider
- is the main data provider in dxchart5-react
. It's responsible for historical candles, last candle updates, quotes, etc.
NOTE: it is the only entry point for your candles data.
Example
/** Copyright ©2024 Devexperts LLC.All rights reserved. Any unauthorized use will constitute an infringement of copyright.In case of any questions regarding types of use, please contact legal@devexperts.com.This notice must remain intact.**/import { aggregationPeriodToString, periodToMinutes, } from '@dx-private/dxchart5-react/dist/chart/model/aggregation.model';import { generateCandles } from '@dx-private/dxchart5-react/dist/utils/generator/candle-generator.utils';import { createPriceDiffGenerator } from '@dx-private/dxchart5-react/dist/utils/last-candle-price-generator';import { hashCode } from '@devexperts/dxcharts-lite/dist/chart/utils/string.utils';const NUMBER_OF_CANDLES = 500;/*** Creates mock implementation of {@link ChartDataProvider}.*/export const createMockChartDataProvider = () => {//#region candles// First of all we should prepare the mock candles data.// To speed up that step we can use `generateCandles` utility from `dxchart5-react`.// `generateMockData` function will generate new candles data for a given symbol/aggregation pair.const generateMockData = (symbol, aggregation, fromTime, toTime) => generateCandles({period: periodToMinutes(aggregation) * 60 * 1000,randomSeed: hashCode(symbol) + hashCode(aggregationPeriodToString(aggregation)),startTimestamp: fromTime,endTimestamp: toTime,quantity: 500,startPrice: 50,candleSize: 1,candleDiversity: 4,highLowSize: 0.2,highLowDiversity: 2,volumeSize: 10000,volumeDiversity: 50000,}).map(candle => ({time: candle.timestamp,open: candle.open,high: candle.hi,low: candle.lo,close: candle.close,volume: candle.volume,impVolatility: 0,vwap: 0,}));// we store last history candles to use them as reference values for// last candle real time updates generatorconst lastHistoryCandles = new Map();// mock global brokerage time to synchronize instruments last candle ticklet globalClockInSeconds = 0;setInterval(() => {globalClockInSeconds += 1;}, 1000);// logic to mimic last candle real time tick with random generatorconst lastCandleSubscriptions = new Map();const priceDiffGenerator = createPriceDiffGenerator();const startLastCandleUpdateTicker = (symbol, aggregation, subscriptionId, callback) => {const aggregationInSeconds = periodToMinutes(aggregation) * 60;lastCandleSubscriptions.set(subscriptionId, setInterval(() => {var _a;const lastCandle = lastHistoryCandles.get(symbol);if (!lastCandle) {console.warn('No last candle for symbol ', symbol, ' found');return;}const priceDiff = (_a = priceDiffGenerator.next().value) !== null && _a !== void 0 ? _a : 1;const lastCandleUpdate = Object.assign(Object.assign({}, lastCandle), { close: lastCandle.close * priceDiff });// # logic to add candle to data series when aggregation period exceeds// the thing is, that `dxcharts-react` will automatically add new candle to the end of data series on chart// if the timestamp of new last candle tick is bigger then it was on the previous last candle tick// and difference in time is >= selected aggregation periodif (globalClockInSeconds % aggregationInSeconds === 0) {lastCandleUpdate.time = lastCandleUpdate.time + aggregationInSeconds * 1000;}lastHistoryCandles.set(symbol, lastCandleUpdate);callback([lastCandleUpdate]);}, 1000));};/*** `requestHistoryData` - `dxcharts-react` requests the historical candles data for a given* symbol/aggregation/etc. combination + handles lazy load requests** @see compare charts are handled here too, for example:* Imagine AAPL as main instrument and GOOG as compare instrument are selected on chart.* Because of this `requestHistoryData` will be called 2 times - first for AAPL, then for GOOG.* Order of call is the same as the order of instruments added, where main instrument is *always* `first`.** @see with multicharts it is still the same.* `requestHistoryData` will be called in order of charts, for example:* on 1st chart AAPL and GOOG are selected, on 2nd chart AMD and NVDA are selected.* Because of that first `requestHistoryData` will be called first for 1st chart for AAPL and GOOG,* secondly `requestHistoryData` will be called for 2nd chart for AMD and NVDA*/const requestHistoryData = (symbol, aggregation,/*** `fromTime` and `toTime` are candles timestamps, that simply reflect the current state of the chart.** `extendedHours` is a flag that indicates whether candles from NOT REGULAR trading session required in addition to ones from REGULAR.** `priceType` is a flag that tells which price ('bid' | 'ask' | 'last' | 'mark') should be used for candles.** `priceIncrement` is a flag that describes which minimum price change should be reflected in candles data.*/options) => {// when `toTime` option is provided - it is indicator for lazy loading functionality// `toTime` means that you should provide candles data, where the rightmost candle will be <= `toTime`.let toTime = options === null || options === void 0 ? void 0 : options.toTime;// `fromTime` often is not provided, because you can give any amount of candles data you want,// but if provided - it means that user requests data *at least* from `fromTime`, i.e leftmost candle should be <= `fromTime`.let fromTime = options === null || options === void 0 ? void 0 : options.fromTime;if (!toTime) {toTime = Date.now();}if (!fromTime) {fromTime = toTime - periodToMinutes(aggregation) * 60 * 1000 * NUMBER_OF_CANDLES;}const data = generateMockData(symbol, aggregation, fromTime, toTime);if (data.length > 0) {lastHistoryCandles.set(symbol, data[data.length - 1]);}return new Promise(resolve => {setTimeout(() => resolve(data), 3000);});};/*** `subscribeCandles` - `dxcharts-react` requests the last candle updates data for a particular* instrument/aggregation/.etc combination.** updates should be passed to the `callback` each time the last candle data is updated.** @see `subscriptionId` helps to identify the combination subscription.* `subscriptionId` arg can be useful to control multiple instrument's* last candle updates at a time** @see compare charts are handled here too, similar to `requestHistoryData`.** @see multicharts are handled here exactly the same way as for `requestHistoryData`.*/const subscribeCandles = (symbol, aggregation, subscriptionId, callback) => {startLastCandleUpdateTicker(symbol, aggregation, subscriptionId, callback);};/*** `unsubscribeCandles` - `dxcharts-react` fires that method when it no longer needs the last candle updates* for a given `subscriptionId`* @see `subscriptionId` is an `id` that was provided by {@link subscribeCandles} method*/const unsubscribeCandles = (subscriptionId) => {const intervalId = lastCandleSubscriptions.get(subscriptionId);if (!intervalId) {console.warn('No last candle generator found for subscriptionId ', subscriptionId);return;}clearInterval(intervalId);lastCandleSubscriptions.delete(subscriptionId);};//#endregion candles//#region service dataconst serviceDataSubscriptions = new Map();/*** Service data is a data that provides some additional data to candles data,* like bid/ask prices, prevDayClosePrice etc.*/const subscribeServiceData = (symbol, callback) => {serviceDataSubscriptions.set(symbol, setInterval(() => {const lastCandle = lastHistoryCandles.get(symbol);if (!lastCandle) {return;}callback({prevDayClosePrice: lastCandle.open / 2,prePostMarketClose: lastCandle.open / 2,bid: lastCandle.close + lastCandle.close * 0.1,ask: lastCandle.close - lastCandle.close * 0.1,});}, 1000));};const unsubscribeServiceData = (symbol) => {const intervalId = serviceDataSubscriptions.get(symbol);if (!intervalId) {console.warn('No service data found for symbol ', symbol);return;}clearInterval(intervalId);serviceDataSubscriptions.delete(symbol);};//#endregion service datareturn {requestHistoryData,subscribeCandles,unsubscribeCandles,subscribeServiceData,unsubscribeServiceData,};};
More details regarding requestHistoryData
The information below might be useful to help understanding the data fetch process inside candles provider
dxchart5-react
is a data-source agnostic library, any arguments dxchart5-react
provides to data provider methods are not query parameters to the data source in most cases.
fromTime
and toTime
arguments simply reflect the current state of the chart, not the chart request to load additional data.
Therefore, you can either use the current state of the chart to calculate the query parameters you need for your data source, or completely ignore them.
Both options:
fromTime
andtoTime
are optional and could beundefined
.
fromTime
As was said, fromTime
is an optional argument and not passed to options
argument in most of the cases at all.
Also, there is NO difference between the undefined
and 0
value. When fromTime
is undefined || 0
(in most of the cases) it shouldn't be interpreted by the provider code as that data should be fetched since the beginning of that instrument.
On the very first load, when user haven't performed any scrolling actions and there's no any user-related/layout-related data from previous chart usage, you can just load latest dataset.
In this case, the rightmost candle will be the current moment - Date.now()
and the leftmost candle will be any date in the past you want to be loaded.
On the second load, when chart was already loaded with some data (after very first load), you'll receive fromTime
argument which is the leftmost candle timestamp of previously loaded data.
In addition, fromTime
is usually used by timeframe presets functionality, so you will see fromTime
parameter provided when user selects some timeframe preset.
toTime
For lazy loading only toTime
is important because it identifies the oldest candle already loaded and passed to dxchart5-react
to determine till which timestamp more candles need to be loaded.
Number of lazy loaded candles can be defined by the provider itself.
If user will scroll the chart to the left, deep in the history data, your provider will receive toTime
argument which will be a timestamp of leftmost candle in viewport.
So, to load additional set of candles you should use toTime
as a rightmost cut-off of data, and leftmost cut-off you can calculate based on the desired amount of data.
updateCallback
updateCallback
allows to completely override initial history data if you need to update it after some period of time.
Since
updateCallback
function overrides all history data, make sure you provided all history data with updates, NOT just updates.
When chart asks you a data (for example on load or when you change aggregation/instrument) via requestHistoryData
you should provide history data via return value, pretty simple.
BUT when you want to change data after some time after initial requestHistoryData
was called, then you should use updateCallback
.
// ...your `ChartDataProvider` code
const initialHistoryDataForSelectedInstrument = [...]
// store `updateCallback` somewhere to access it out of requestHistoryData scope if you need
let updateHistoryCallback = undefined;
// imagine that after some period of time you need to update some part of history data for selected instrument
setTimeout(() => {
const historyDataUpdates: ChartCandleData[] = [
{ time: 1719218004, open: 10, high: 20, low: 10, close: 15, volume: 30000 },
];
// implement your function that will merge desired history data updates with initial history data
const mergedHistoryData = yourMergeFunction(initialHistoryDataForSelectedInstrument, historyDataUpdates)
// then pass merged data to `updateCallback`
updateHistoryCallback(mergedHistoryData)
}, 10000);
const requestHistoryData = (
symbol: string,
aggregation: AggregationPeriod,
options: RequestMoreDataOptions & ChartDataOptions,
updateCallback: (data: ChartCandleData[]) => void,
chartId: string,
) => {
updateHistoryCallback = updateCallback;
// average provider code,
// same as in the example above
let toTime = options?.toTime;
let fromTime = options?.fromTime;
if (!toTime) {
toTime = Date.now();
}
if (!fromTime) {
fromTime = toTime - periodToMinutes(aggregation) * 60 * 1000 * NUMBER_OF_CANDLES;
}
const data = initialHistoryDataForSelectedInstrument;
return new Promise<ChartCandleData[]>(resolve => {
setTimeout(() => resolve(data), 3000);
});
};
requestHistoryData
provides ChartCandleData[]
, that have some options for indicators, for example vwap
field, that provides calculated data for Volume Weighted Average Price
indicator.
Please note that the VWAP indicator requires trading session data for its calculations, regardless of whether you are providing the data for VWAP in candle or not. Therefore, you need to create a
TradingSessionsProvider
using data generated fromgenerateSessions
.
const historyDataMap = (candle: Candle) => ({
time: candle.timestamp,
open: candle.open,
high: candle.hi,
low: candle.lo,
close: candle.close,
volume: candle.volume,
impVolatility: 0,
vwap: 0, // here you can provide your own vwap calculation or provide from other source, otherwise you can left this field undefined
});
API Reference
ChartDataProvider
- ChartDataProvider.requestHistoryData
- Parameters
- symbol: string
- aggregation: AggregationPeriod
- options: { fromTime?: number; toTime?: number; } & ChartDataOptions
- `fromTime` and `toTime` are candles timestamps, that simply reflect the current state of the chart. Therefore, you can either use the current state of the chart to calculate the necessary query parameters, or completely ignore them. `fromTime` is an oldest candle's timestamp which should be data get from, `toTime` is the newest (usually `toTime` candle equals the oldest visible candle)
- updateCallback: (data: ChartCandleData[]) => void
- !!! WARNING !!! This function updates the whole instrument's history data
- chartId: string
- Returns
- Promise<ChartCandleData[]>
ChartDataProvider.requestHistoryData(symbol: string, aggregation: AggregationPeriod, options: { fromTime?: number; toTime?: number; } & ChartDataOptions, updateCallback: (data: ChartCandleData[]) => void, chartId: string): Promise<ChartCandleData[]>
There might be 3 possible cases: 1. Initial data request when left bound of viewport somewhere in the history data and right bound at current candle tick. In this situation `requestHistoryData` method will provide only `fromTime` parameter (that's a base case for initial viewport). In other words `fromTime` will point somewhere on the left bound of viewport. That means that in this case you should provide history data AT LEAST from `fromTime` to right now, the time when request were made. BUT you can give more history data if you want. 2. lnitial data request when left and right bound of viewport somewhere in the history data. This can happen when user for example reloads the page when their viewport somewhere in the history data. In this situation `requestHistoryData` will provide only `fromTime` parameter too. It means that even in this case you should provide all the history data from `fromTime` to right now, the time when request were made. That's pretty much the same case as №1. 3. Lazy load of candles, that are on the left to timeline, i.e older history data. On initial load you can give chart any amount of candles you like, it might be 100 or 1000, doesn't matter. Depends on you. This means that when we scroll to the left to older data, we will have as much candles as you provided. When we will reach the oldest history candle, chart will fire `requestHistoryData` to request even older data, i.e lazy load. In this situation `requestHistoryData` will only provide `toTime`. Why this way? Because again, you can give chart any amount of data you want, so there's no point in `fromTime` parameter. You may not give any data in the response - this would mean that there's no history data left.
- ChartDataProvider.subscribeCandles
- Parameters
- symbol: string
- aggregation: AggregationPeriod
- subscriptionId: string
- subscribeCallback: (data: ChartCandleData[]) => void
- options: ChartDataOptions
- chartId: string
- Returns
- void
ChartDataProvider.subscribeCandles(symbol: string, aggregation: AggregationPeriod, subscriptionId: string, subscribeCallback: (data: ChartCandleData[]) => void, options: ChartDataOptions, chartId: string): void
- ChartDataProvider.unsubscribeCandles
- Parameters
- subscriptionId: string
- Returns
- void
ChartDataProvider.unsubscribeCandles(subscriptionId: string): void
Unsubscribe is called when symbol, aggregation or options are changed
- ChartDataProvider.subscribeServiceData
- Parameters
- symbol: string
- subscribeCallback: (data: ServiceData) => void
- Returns
- void
ChartDataProvider.subscribeServiceData(symbol: string, subscribeCallback: (data: ServiceData) => void): void
- ChartDataProvider.unsubscribeServiceData
- Parameters
- symbol: string
- Returns
- void
ChartDataProvider.unsubscribeServiceData(symbol: string): void