jenkins
2023-02-14 12:25:28 +00:00
parent e94bed7dc5
commit 26c95a82aa
815 changed files with 10833 additions and 2253 deletions

File diff suppressed because one or more lines are too long

View File

@@ -28,7 +28,8 @@ export class DataPulseProvider {
return;
}
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(() => {

View File

@@ -1,8 +1,9 @@
import { getErrorMessage, } from './helpers';
export class HistoryProvider {
constructor(datafeedUrl, requester) {
constructor(datafeedUrl, requester, limitedServerResponse) {
this._datafeedUrl = datafeedUrl;
this._requester = requester;
this._limitedServerResponse = limitedServerResponse;
}
getBars(symbolInfo, resolution, periodParams) {
const requestParams = {
@@ -20,54 +21,103 @@ export class HistoryProvider {
if (symbolInfo.unit_id !== undefined) {
requestParams.unitId = symbolInfo.unit_id;
}
return new Promise((resolve, reject) => {
this._requester.sendRequest(this._datafeedUrl, 'history', requestParams)
.then((response) => {
if (response.s !== 'ok' && response.s !== 'no_data') {
reject(response.errmsg);
return;
return new Promise(async (resolve, reject) => {
try {
const initialResponse = await this._requester.sendRequest(this._datafeedUrl, 'history', requestParams);
const result = this._processHistoryResponse(initialResponse);
if (this._limitedServerResponse) {
await this._processTruncatedResponse(result, requestParams);
}
const bars = [];
const meta = {
noData: false,
};
if (response.s === 'no_data') {
meta.noData = true;
meta.nextTime = response.nextTime;
resolve(result);
}
catch (e) {
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);
}
else {
const volumePresent = response.v !== undefined;
const ohlPresent = response.o !== undefined;
for (let i = 0; i < response.t.length; ++i) {
const barValue = {
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.o[i]);
barValue.high = parseFloat(response.h[i]);
barValue.low = parseFloat(response.l[i]);
}
if (volumePresent) {
barValue.volume = parseFloat(response.v[i]);
}
bars.push(barValue);
}
}
resolve({
bars: bars,
meta: meta,
});
})
.catch((reason) => {
const reasonString = getErrorMessage(reason);
// tslint:disable-next-line:no-console
console.warn(`HistoryProvider: getBars() failed, error=${reasonString}`);
reject(reasonString);
});
}
});
}
async _processTruncatedResponse(result, 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 - 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(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) {
/**
* 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}`);
}
}
}
_processHistoryResponse(response) {
if (response.s !== 'ok' && response.s !== 'no_data') {
throw new Error(response.errmsg);
}
const bars = [];
const meta = {
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 = {
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.o[i]);
barValue.high = parseFloat(response.h[i]);
barValue.low = parseFloat(response.l[i]);
}
if (volumePresent) {
barValue.volume = parseFloat(response.v[i]);
}
bars.push(barValue);
}
}
return {
bars: bars,
meta: meta,
};
}
}

View File

@@ -40,7 +40,8 @@ export class QuotesPulseProvider {
if (this._requestsPending > 0) {
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];
this._quotesProvider.getQuotes(updateType === 1 /* SymbolsType.Fast */ ? subscriptionRecord.fastSymbols : subscriptionRecord.symbols)

View File

@@ -21,6 +21,7 @@ export class Requester {
if (this._headers !== undefined) {
options.headers = this._headers;
}
// eslint-disable-next-line no-restricted-globals
return fetch(`${datafeedUrl}/${urlPath}`, options)
.then((response) => response.text())
.then((responseTest) => JSON.parse(responseTest));

View File

@@ -12,12 +12,12 @@ function extractField(data, field, arrayIndex) {
* See UDF protocol reference at https://github.com/tradingview/charting_library/wiki/UDF
*/
export class UDFCompatibleDatafeedBase {
constructor(datafeedURL, quotesProvider, requester, updateFrequency = 10 * 1000) {
constructor(datafeedURL, quotesProvider, requester, updateFrequency = 10 * 1000, limitedServerResponse) {
this._configuration = defaultConfiguration();
this._symbolsStorage = null;
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);
this._quotesPulseProvider = new QuotesPulseProvider(this._quotesProvider);
@@ -198,7 +198,7 @@ export class UDFCompatibleDatafeedBase {
original_unit_id: (_g = response.original_unit_id) !== null && _g !== void 0 ? _g : response['original-unit-id'],
unit_conversion_types: (_h = response.unit_conversion_types) !== null && _h !== void 0 ? _h : response['unit-conversion-types'],
has_intraday: (_k = (_j = response.has_intraday) !== null && _j !== void 0 ? _j : response['has-intraday']) !== null && _k !== void 0 ? _k : false,
// tslint:disable-next-line: no-deprecation
// eslint-disable-next-line deprecation/deprecation
has_no_volume: (_l = response.has_no_volume) !== null && _l !== void 0 ? _l : response['has-no-volume'],
visible_plots_set: (_m = response.visible_plots_set) !== null && _m !== void 0 ? _m : response['visible-plots-set'],
minmov: (_p = (_o = response.minmovement) !== null && _o !== void 0 ? _o : response.minmov) !== null && _p !== void 0 ? _p : 0,

View File

@@ -2,9 +2,9 @@ import { UDFCompatibleDatafeedBase } from './udf-compatible-datafeed-base';
import { QuotesProvider } from './quotes-provider';
import { Requester } from './requester';
export class UDFCompatibleDatafeed extends UDFCompatibleDatafeedBase {
constructor(datafeedURL, updateFrequency = 10 * 1000) {
constructor(datafeedURL, updateFrequency = 10 * 1000, limitedServerResponse) {
const requester = new Requester();
const quotesProvider = new QuotesProvider(datafeedURL, requester);
super(datafeedURL, quotesProvider, requester, updateFrequency);
super(datafeedURL, quotesProvider, requester, updateFrequency, limitedServerResponse);
}
}

View File

@@ -7,7 +7,7 @@
"@rollup/plugin-node-resolve": "~9.0.0",
"rollup": "~2.28.2",
"rollup-plugin-terser": "~7.0.2",
"typescript": "4.7.3"
"typescript": "4.9.5"
},
"scripts": {
"compile": "tsc",

View File

@@ -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(() => {

View File

@@ -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,
};
}
}

View File

@@ -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];

View File

@@ -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));

View File

@@ -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,

View File

@@ -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);
}
}