Release v24.000 (from b7d8dd93)
Fixes tradingview/charting_library#60 Fixes tradingview/charting_library#65 Fixes tradingview/charting_library#70 Fixes tradingview/charting_library#71 Fixes tradingview/charting_library#75 Fixes tradingview/charting_library#76 Fixes tradingview/charting_library#78 Fixes tradingview/charting_library#79 Fixes tradingview/charting_library#81 Fixes tradingview/charting_library#82 Fixes tradingview/charting_library#84 Fixes tradingview/charting_library#86 Fixes tradingview/charting_library#89 Fixes tradingview/charting_library#90 Fixes tradingview/charting_library#91 Fixes tradingview/charting_library#92 Fixes tradingview/charting_library#94 Fixes tradingview/charting_library#99 Fixes tradingview/charting_library#100 Fixes tradingview/charting_library#101 Fixes tradingview/charting_library#102 Fixes tradingview/charting_library#103 Fixes tradingview/charting_library#1995 Fixes tradingview/charting_library#5726 Fixes tradingview/charting_library#6025 Fixes tradingview/charting_library#6406 Fixes tradingview/charting_library#6636 Fixes tradingview/charting_library#6767 Fixes tradingview/charting_library#6775 Fixes tradingview/charting_library#6783 Fixes tradingview/charting_library#6864 Fixes tradingview/charting_library#6926 Fixes tradingview/charting_library#7060 Fixes tradingview/charting_library#7169 Fixes tradingview/charting_library#7307
This commit is contained in:
@@ -61,7 +61,8 @@ export class DataPulseProvider {
|
||||
}
|
||||
|
||||
this._requestsPending = 0;
|
||||
for (const listenerGuid in this._subscribers) { // tslint:disable-line:forin
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const listenerGuid in this._subscribers) {
|
||||
this._requestsPending += 1;
|
||||
this._updateDataForSubscriber(listenerGuid)
|
||||
.then(() => {
|
||||
|
||||
@@ -47,16 +47,43 @@ export interface GetBarsResult {
|
||||
meta: HistoryMetadata;
|
||||
}
|
||||
|
||||
export interface LimitedResponseConfiguration {
|
||||
/**
|
||||
* Set this value to the maximum number of bars which
|
||||
* the data backend server can supply in a single response.
|
||||
* This doesn't affect or change the library behavior regarding
|
||||
* how many bars it will request. It just allows this Datafeed
|
||||
* implementation to correctly handle this situation.
|
||||
*/
|
||||
maxResponseLength: number;
|
||||
/**
|
||||
* If the server can't return all the required bars in a single
|
||||
* response then `expectedOrder` specifies whether the server
|
||||
* will send the latest (newest) or earliest (older) data first.
|
||||
*/
|
||||
expectedOrder: 'latestFirst' | 'earliestFirst';
|
||||
}
|
||||
|
||||
export class HistoryProvider {
|
||||
private _datafeedUrl: string;
|
||||
private readonly _requester: Requester;
|
||||
private readonly _limitedServerResponse?: LimitedResponseConfiguration;
|
||||
|
||||
public constructor(datafeedUrl: string, requester: Requester) {
|
||||
public constructor(
|
||||
datafeedUrl: string,
|
||||
requester: Requester,
|
||||
limitedServerResponse?: LimitedResponseConfiguration
|
||||
) {
|
||||
this._datafeedUrl = datafeedUrl;
|
||||
this._requester = requester;
|
||||
this._limitedServerResponse = limitedServerResponse;
|
||||
}
|
||||
|
||||
public getBars(symbolInfo: LibrarySymbolInfo, resolution: string, periodParams: PeriodParamsWithOptionalCountback): Promise<GetBarsResult> {
|
||||
public getBars(
|
||||
symbolInfo: LibrarySymbolInfo,
|
||||
resolution: string,
|
||||
periodParams: PeriodParamsWithOptionalCountback
|
||||
): Promise<GetBarsResult> {
|
||||
const requestParams: RequestParams = {
|
||||
symbol: symbolInfo.ticker || '',
|
||||
resolution: resolution,
|
||||
@@ -75,60 +102,128 @@ export class HistoryProvider {
|
||||
requestParams.unitId = symbolInfo.unit_id;
|
||||
}
|
||||
|
||||
return new Promise((resolve: (result: GetBarsResult) => void, reject: (reason: string) => void) => {
|
||||
this._requester.sendRequest<HistoryResponse>(this._datafeedUrl, 'history', requestParams)
|
||||
.then((response: HistoryResponse | UdfErrorResponse) => {
|
||||
if (response.s !== 'ok' && response.s !== 'no_data') {
|
||||
reject(response.errmsg);
|
||||
return;
|
||||
return new Promise(
|
||||
async (
|
||||
resolve: (result: GetBarsResult) => void,
|
||||
reject: (reason: string) => void
|
||||
) => {
|
||||
try {
|
||||
const initialResponse = await this._requester.sendRequest<HistoryResponse>(
|
||||
this._datafeedUrl,
|
||||
'history',
|
||||
requestParams
|
||||
);
|
||||
const result = this._processHistoryResponse(initialResponse);
|
||||
|
||||
if (this._limitedServerResponse) {
|
||||
await this._processTruncatedResponse(result, requestParams);
|
||||
}
|
||||
|
||||
const bars: Bar[] = [];
|
||||
const meta: HistoryMetadata = {
|
||||
noData: false,
|
||||
};
|
||||
|
||||
if (response.s === 'no_data') {
|
||||
meta.noData = true;
|
||||
meta.nextTime = response.nextTime;
|
||||
} else {
|
||||
const volumePresent = response.v !== undefined;
|
||||
const ohlPresent = response.o !== undefined;
|
||||
|
||||
for (let i = 0; i < response.t.length; ++i) {
|
||||
const barValue: Bar = {
|
||||
time: response.t[i] * 1000,
|
||||
close: parseFloat(response.c[i]),
|
||||
open: parseFloat(response.c[i]),
|
||||
high: parseFloat(response.c[i]),
|
||||
low: parseFloat(response.c[i]),
|
||||
};
|
||||
|
||||
if (ohlPresent) {
|
||||
barValue.open = parseFloat((response as HistoryFullDataResponse).o[i]);
|
||||
barValue.high = parseFloat((response as HistoryFullDataResponse).h[i]);
|
||||
barValue.low = parseFloat((response as HistoryFullDataResponse).l[i]);
|
||||
}
|
||||
|
||||
if (volumePresent) {
|
||||
barValue.volume = parseFloat((response as HistoryFullDataResponse).v[i]);
|
||||
}
|
||||
|
||||
bars.push(barValue);
|
||||
}
|
||||
resolve(result);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error || typeof e === 'string') {
|
||||
const reasonString = getErrorMessage(e);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn(
|
||||
`HistoryProvider: getBars() failed, error=${reasonString}`
|
||||
);
|
||||
reject(reasonString);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
resolve({
|
||||
bars: bars,
|
||||
meta: meta,
|
||||
});
|
||||
})
|
||||
.catch((reason?: string | Error) => {
|
||||
const reasonString = getErrorMessage(reason);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn(`HistoryProvider: getBars() failed, error=${reasonString}`);
|
||||
reject(reasonString);
|
||||
});
|
||||
});
|
||||
private async _processTruncatedResponse(result: GetBarsResult, requestParams: RequestParams) {
|
||||
let lastResultLength = result.bars.length;
|
||||
try {
|
||||
while (this._limitedServerResponse &&
|
||||
this._limitedServerResponse.maxResponseLength > 0 &&
|
||||
this._limitedServerResponse.maxResponseLength === lastResultLength &&
|
||||
requestParams.from < requestParams.to) {
|
||||
// adjust request parameters for follow-up request
|
||||
if (requestParams.countback) {
|
||||
requestParams.countback = (requestParams.countback as number) - lastResultLength;
|
||||
}
|
||||
if (this._limitedServerResponse.expectedOrder === 'earliestFirst') {
|
||||
requestParams.from = Math.round(result.bars[result.bars.length - 1].time / 1000);
|
||||
} else {
|
||||
requestParams.to = Math.round(result.bars[0].time / 1000);
|
||||
}
|
||||
|
||||
const followupResponse = await this._requester.sendRequest<HistoryResponse>(
|
||||
this._datafeedUrl,
|
||||
'history',
|
||||
requestParams
|
||||
);
|
||||
const followupResult = this._processHistoryResponse(
|
||||
followupResponse
|
||||
);
|
||||
lastResultLength = followupResult.bars.length;
|
||||
// merge result with results collected so far
|
||||
if (this._limitedServerResponse.expectedOrder === 'earliestFirst') {
|
||||
result.bars.push(...followupResult.bars);
|
||||
} else {
|
||||
result.bars.unshift(...followupResult.bars);
|
||||
}
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
/**
|
||||
* Error occurred during followup request. We won't reject the original promise
|
||||
* because the initial response was valid so we will return what we've got so far.
|
||||
*/
|
||||
if (e instanceof Error || typeof e === 'string') {
|
||||
const reasonString = getErrorMessage(e);
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn(
|
||||
`HistoryProvider: getBars() warning during followup request, error=${reasonString}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _processHistoryResponse(response: HistoryResponse | UdfErrorResponse) {
|
||||
if (response.s !== 'ok' && response.s !== 'no_data') {
|
||||
throw new Error(response.errmsg);
|
||||
}
|
||||
|
||||
const bars: Bar[] = [];
|
||||
const meta: HistoryMetadata = {
|
||||
noData: false,
|
||||
};
|
||||
|
||||
if (response.s === 'no_data') {
|
||||
meta.noData = true;
|
||||
meta.nextTime = response.nextTime;
|
||||
} else {
|
||||
const volumePresent = response.v !== undefined;
|
||||
const ohlPresent = response.o !== undefined;
|
||||
|
||||
for (let i = 0; i < response.t.length; ++i) {
|
||||
const barValue: Bar = {
|
||||
time: response.t[i] * 1000,
|
||||
close: parseFloat(response.c[i]),
|
||||
open: parseFloat(response.c[i]),
|
||||
high: parseFloat(response.c[i]),
|
||||
low: parseFloat(response.c[i]),
|
||||
};
|
||||
|
||||
if (ohlPresent) {
|
||||
barValue.open = parseFloat((response as HistoryFullDataResponse).o[i]);
|
||||
barValue.high = parseFloat((response as HistoryFullDataResponse).h[i]);
|
||||
barValue.low = parseFloat((response as HistoryFullDataResponse).l[i]);
|
||||
}
|
||||
|
||||
if (volumePresent) {
|
||||
barValue.volume = parseFloat((response as HistoryFullDataResponse).v[i]);
|
||||
}
|
||||
|
||||
bars.push(barValue);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bars: bars,
|
||||
meta: meta,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,8 @@ export class QuotesPulseProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const listenerGuid in this._subscribers) { // tslint:disable-line:forin
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const listenerGuid in this._subscribers) {
|
||||
this._requestsPending++;
|
||||
|
||||
const subscriptionRecord = this._subscribers[listenerGuid];
|
||||
|
||||
@@ -32,6 +32,7 @@ export class Requester {
|
||||
options.headers = this._headers;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return fetch(`${datafeedUrl}/${urlPath}`, options)
|
||||
.then((response: Response) => response.text())
|
||||
.then((responseTest: string) => JSON.parse(responseTest));
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import {
|
||||
GetBarsResult,
|
||||
HistoryProvider,
|
||||
type LimitedResponseConfiguration,
|
||||
PeriodParamsWithOptionalCountback,
|
||||
} from './history-provider';
|
||||
|
||||
@@ -121,10 +122,20 @@ export class UDFCompatibleDatafeedBase implements IExternalDatafeed, IDatafeedQu
|
||||
|
||||
private readonly _requester: Requester;
|
||||
|
||||
protected constructor(datafeedURL: string, quotesProvider: IQuotesProvider, requester: Requester, updateFrequency: number = 10 * 1000) {
|
||||
protected constructor(
|
||||
datafeedURL: string,
|
||||
quotesProvider: IQuotesProvider,
|
||||
requester: Requester,
|
||||
updateFrequency: number = 10 * 1000,
|
||||
limitedServerResponse?: LimitedResponseConfiguration
|
||||
) {
|
||||
this._datafeedURL = datafeedURL;
|
||||
this._requester = requester;
|
||||
this._historyProvider = new HistoryProvider(datafeedURL, this._requester);
|
||||
this._historyProvider = new HistoryProvider(
|
||||
datafeedURL,
|
||||
this._requester,
|
||||
limitedServerResponse
|
||||
);
|
||||
this._quotesProvider = quotesProvider;
|
||||
|
||||
this._dataPulseProvider = new DataPulseProvider(this._historyProvider, updateFrequency);
|
||||
@@ -331,7 +342,7 @@ export class UDFCompatibleDatafeedBase implements IExternalDatafeed, IDatafeedQu
|
||||
original_unit_id: response.original_unit_id ?? response['original-unit-id'],
|
||||
unit_conversion_types: response.unit_conversion_types ?? response['unit-conversion-types'],
|
||||
has_intraday: response.has_intraday ?? response['has-intraday'] ?? false,
|
||||
// tslint:disable-next-line: no-deprecation
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
has_no_volume: response.has_no_volume ?? response['has-no-volume'],
|
||||
visible_plots_set: response.visible_plots_set ?? response['visible-plots-set'],
|
||||
minmov: response.minmovement ?? response.minmov ?? 0,
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { UDFCompatibleDatafeedBase } from './udf-compatible-datafeed-base';
|
||||
import { QuotesProvider } from './quotes-provider';
|
||||
import { Requester } from './requester';
|
||||
import { type LimitedResponseConfiguration } from './history-provider';
|
||||
|
||||
export class UDFCompatibleDatafeed extends UDFCompatibleDatafeedBase {
|
||||
public constructor(datafeedURL: string, updateFrequency: number = 10 * 1000) {
|
||||
public constructor(
|
||||
datafeedURL: string,
|
||||
updateFrequency: number = 10 * 1000,
|
||||
limitedServerResponse?: LimitedResponseConfiguration
|
||||
) {
|
||||
const requester = new Requester();
|
||||
const quotesProvider = new QuotesProvider(datafeedURL, requester);
|
||||
super(datafeedURL, quotesProvider, requester, updateFrequency);
|
||||
super(datafeedURL, quotesProvider, requester, updateFrequency, limitedServerResponse);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user