Release v20.028 (from a477abd4)

This commit is contained in:
jenkins@tradingview.com
2021-09-10 14:56:00 +00:00
parent 017382d7b8
commit a0f6900107
623 changed files with 2115 additions and 2145 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,14 @@
import { getErrorMessage, logMessage, } from './helpers';
var DataPulseProvider = /** @class */ (function () {
function DataPulseProvider(historyProvider, updateFrequency) {
export class DataPulseProvider {
constructor(historyProvider, updateFrequency) {
this._subscribers = {};
this._requestsPending = 0;
this._historyProvider = historyProvider;
setInterval(this._updateData.bind(this), updateFrequency);
}
DataPulseProvider.prototype.subscribeBars = function (symbolInfo, resolution, newDataCallback, listenerGuid) {
subscribeBars(symbolInfo, resolution, newDataCallback, listenerGuid) {
if (this._subscribers.hasOwnProperty(listenerGuid)) {
logMessage("DataPulseProvider: already has subscriber with id=" + listenerGuid);
logMessage(`DataPulseProvider: already has subscriber with id=${listenerGuid}`);
return;
}
this._subscribers[listenerGuid] = {
@@ -17,85 +17,77 @@ var DataPulseProvider = /** @class */ (function () {
resolution: resolution,
symbolInfo: symbolInfo,
};
logMessage("DataPulseProvider: subscribed for #" + listenerGuid + " - {" + symbolInfo.name + ", " + resolution + "}");
};
DataPulseProvider.prototype.unsubscribeBars = function (listenerGuid) {
logMessage(`DataPulseProvider: subscribed for #${listenerGuid} - {${symbolInfo.name}, ${resolution}}`);
}
unsubscribeBars(listenerGuid) {
delete this._subscribers[listenerGuid];
logMessage("DataPulseProvider: unsubscribed for #" + listenerGuid);
};
DataPulseProvider.prototype._updateData = function () {
var _this = this;
logMessage(`DataPulseProvider: unsubscribed for #${listenerGuid}`);
}
_updateData() {
if (this._requestsPending > 0) {
return;
}
this._requestsPending = 0;
var _loop_1 = function (listenerGuid) {
this_1._requestsPending += 1;
this_1._updateDataForSubscriber(listenerGuid)
.then(function () {
_this._requestsPending -= 1;
logMessage("DataPulseProvider: data for #" + listenerGuid + " updated successfully, pending=" + _this._requestsPending);
for (const listenerGuid in this._subscribers) { // tslint:disable-line:forin
this._requestsPending += 1;
this._updateDataForSubscriber(listenerGuid)
.then(() => {
this._requestsPending -= 1;
logMessage(`DataPulseProvider: data for #${listenerGuid} updated successfully, pending=${this._requestsPending}`);
})
.catch(function (reason) {
_this._requestsPending -= 1;
logMessage("DataPulseProvider: data for #" + listenerGuid + " updated with error=" + getErrorMessage(reason) + ", pending=" + _this._requestsPending);
.catch((reason) => {
this._requestsPending -= 1;
logMessage(`DataPulseProvider: data for #${listenerGuid} updated with error=${getErrorMessage(reason)}, pending=${this._requestsPending}`);
});
};
var this_1 = this;
for (var listenerGuid in this._subscribers) {
_loop_1(listenerGuid);
}
};
DataPulseProvider.prototype._updateDataForSubscriber = function (listenerGuid) {
var _this = this;
var subscriptionRecord = this._subscribers[listenerGuid];
var rangeEndTime = parseInt((Date.now() / 1000).toString());
}
_updateDataForSubscriber(listenerGuid) {
const subscriptionRecord = this._subscribers[listenerGuid];
const rangeEndTime = parseInt((Date.now() / 1000).toString());
// BEWARE: please note we really need 2 bars, not the only last one
// see the explanation below. `10` is the `large enough` value to work around holidays
var rangeStartTime = rangeEndTime - periodLengthSeconds(subscriptionRecord.resolution, 10);
const rangeStartTime = rangeEndTime - periodLengthSeconds(subscriptionRecord.resolution, 10);
return this._historyProvider.getBars(subscriptionRecord.symbolInfo, subscriptionRecord.resolution, {
from: rangeStartTime,
to: rangeEndTime,
countBack: 2,
firstDataRequest: false,
})
.then(function (result) {
_this._onSubscriberDataReceived(listenerGuid, result);
.then((result) => {
this._onSubscriberDataReceived(listenerGuid, result);
});
};
DataPulseProvider.prototype._onSubscriberDataReceived = function (listenerGuid, result) {
}
_onSubscriberDataReceived(listenerGuid, result) {
// means the subscription was cancelled while waiting for data
if (!this._subscribers.hasOwnProperty(listenerGuid)) {
logMessage("DataPulseProvider: Data comes for already unsubscribed subscription #" + listenerGuid);
logMessage(`DataPulseProvider: Data comes for already unsubscribed subscription #${listenerGuid}`);
return;
}
var bars = result.bars;
const bars = result.bars;
if (bars.length === 0) {
return;
}
var lastBar = bars[bars.length - 1];
var subscriptionRecord = this._subscribers[listenerGuid];
const lastBar = bars[bars.length - 1];
const subscriptionRecord = this._subscribers[listenerGuid];
if (subscriptionRecord.lastBarTime !== null && lastBar.time < subscriptionRecord.lastBarTime) {
return;
}
var isNewBar = subscriptionRecord.lastBarTime !== null && lastBar.time > subscriptionRecord.lastBarTime;
const isNewBar = subscriptionRecord.lastBarTime !== null && lastBar.time > subscriptionRecord.lastBarTime;
// Pulse updating may miss some trades data (ie, if pulse period = 10 secods and new bar is started 5 seconds later after the last update, the
// old bar's last 5 seconds trades will be lost). Thus, at fist we should broadcast old bar updates when it's ready.
if (isNewBar) {
if (bars.length < 2) {
throw new Error('Not enough bars in history for proper pulse update. Need at least 2.');
}
var previousBar = bars[bars.length - 2];
const previousBar = bars[bars.length - 2];
subscriptionRecord.listener(previousBar);
}
subscriptionRecord.lastBarTime = lastBar.time;
subscriptionRecord.listener(lastBar);
};
return DataPulseProvider;
}());
export { DataPulseProvider };
}
}
function periodLengthSeconds(resolution, requiredPeriodsCount) {
var daysCount = 0;
let daysCount = 0;
if (resolution === 'D' || resolution === '1D') {
daysCount = requiredPeriodsCount;
}

View File

@@ -1,12 +1,12 @@
/**
* If you want to enable logs from datafeed set it to `true`
*/
var isLoggingEnabled = false;
const isLoggingEnabled = false;
export function logMessage(message) {
if (isLoggingEnabled) {
var now = new Date();
const now = new Date();
// tslint:disable-next-line:no-console
console.log(now.toLocaleTimeString() + "." + now.getMilliseconds() + "> " + message);
console.log(`${now.toLocaleTimeString()}.${now.getMilliseconds()}> ${message}`);
}
}
export function getErrorMessage(error) {

View File

@@ -1,12 +1,11 @@
import { getErrorMessage, } from './helpers';
var HistoryProvider = /** @class */ (function () {
function HistoryProvider(datafeedUrl, requester) {
export class HistoryProvider {
constructor(datafeedUrl, requester) {
this._datafeedUrl = datafeedUrl;
this._requester = requester;
}
HistoryProvider.prototype.getBars = function (symbolInfo, resolution, periodParams) {
var _this = this;
var requestParams = {
getBars(symbolInfo, resolution, periodParams) {
const requestParams = {
symbol: symbolInfo.ticker || '',
resolution: resolution,
from: periodParams.from,
@@ -18,15 +17,18 @@ var HistoryProvider = /** @class */ (function () {
if (symbolInfo.currency_code !== undefined) {
requestParams.currencyCode = symbolInfo.currency_code;
}
return new Promise(function (resolve, reject) {
_this._requester.sendRequest(_this._datafeedUrl, 'history', requestParams)
.then(function (response) {
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;
}
var bars = [];
var meta = {
const bars = [];
const meta = {
noData: false,
};
if (response.s === 'no_data') {
@@ -34,10 +36,10 @@ var HistoryProvider = /** @class */ (function () {
meta.nextTime = response.nextTime;
}
else {
var volumePresent = response.v !== undefined;
var ohlPresent = response.o !== undefined;
for (var i = 0; i < response.t.length; ++i) {
var barValue = {
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]),
@@ -60,14 +62,12 @@ var HistoryProvider = /** @class */ (function () {
meta: meta,
});
})
.catch(function (reason) {
var reasonString = getErrorMessage(reason);
.catch((reason) => {
const reasonString = getErrorMessage(reason);
// tslint:disable-next-line:no-console
console.warn("HistoryProvider: getBars() failed, error=" + reasonString);
console.warn(`HistoryProvider: getBars() failed, error=${reasonString}`);
reject(reasonString);
});
});
};
return HistoryProvider;
}());
export { HistoryProvider };
}
}

View File

@@ -1,14 +1,13 @@
import { getErrorMessage, logMessage, } from './helpers';
var QuotesProvider = /** @class */ (function () {
function QuotesProvider(datafeedUrl, requester) {
export class QuotesProvider {
constructor(datafeedUrl, requester) {
this._datafeedUrl = datafeedUrl;
this._requester = requester;
}
QuotesProvider.prototype.getQuotes = function (symbols) {
var _this = this;
return new Promise(function (resolve, reject) {
_this._requester.sendRequest(_this._datafeedUrl, 'quotes', { symbols: symbols })
.then(function (response) {
getQuotes(symbols) {
return new Promise((resolve, reject) => {
this._requester.sendRequest(this._datafeedUrl, 'quotes', { symbols: symbols })
.then((response) => {
if (response.s === 'ok') {
resolve(response.d);
}
@@ -16,13 +15,11 @@ var QuotesProvider = /** @class */ (function () {
reject(response.errmsg);
}
})
.catch(function (error) {
var errorMessage = getErrorMessage(error);
logMessage("QuotesProvider: getQuotes failed, error=" + errorMessage);
reject("network error: " + errorMessage);
.catch((error) => {
const errorMessage = getErrorMessage(error);
logMessage(`QuotesProvider: getQuotes failed, error=${errorMessage}`);
reject(`network error: ${errorMessage}`);
});
});
};
return QuotesProvider;
}());
export { QuotesProvider };
}
}

View File

@@ -1,51 +1,44 @@
import { getErrorMessage, logMessage, } from './helpers';
var QuotesPulseProvider = /** @class */ (function () {
function QuotesPulseProvider(quotesProvider) {
export class QuotesPulseProvider {
constructor(quotesProvider) {
this._subscribers = {};
this._requestsPending = 0;
this._quotesProvider = quotesProvider;
setInterval(this._updateQuotes.bind(this, 1 /* Fast */), 10000 /* Fast */);
setInterval(this._updateQuotes.bind(this, 0 /* General */), 60000 /* General */);
}
QuotesPulseProvider.prototype.subscribeQuotes = function (symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
this._subscribers[listenerGuid] = {
symbols: symbols,
fastSymbols: fastSymbols,
listener: onRealtimeCallback,
};
logMessage("QuotesPulseProvider: subscribed quotes with #" + listenerGuid);
};
QuotesPulseProvider.prototype.unsubscribeQuotes = function (listenerGuid) {
logMessage(`QuotesPulseProvider: subscribed quotes with #${listenerGuid}`);
}
unsubscribeQuotes(listenerGuid) {
delete this._subscribers[listenerGuid];
logMessage("QuotesPulseProvider: unsubscribed quotes with #" + listenerGuid);
};
QuotesPulseProvider.prototype._updateQuotes = function (updateType) {
var _this = this;
logMessage(`QuotesPulseProvider: unsubscribed quotes with #${listenerGuid}`);
}
_updateQuotes(updateType) {
if (this._requestsPending > 0) {
return;
}
var _loop_1 = function (listenerGuid) {
this_1._requestsPending++;
var subscriptionRecord = this_1._subscribers[listenerGuid];
this_1._quotesProvider.getQuotes(updateType === 1 /* Fast */ ? subscriptionRecord.fastSymbols : subscriptionRecord.symbols)
.then(function (data) {
_this._requestsPending--;
if (!_this._subscribers.hasOwnProperty(listenerGuid)) {
for (const listenerGuid in this._subscribers) { // tslint:disable-line:forin
this._requestsPending++;
const subscriptionRecord = this._subscribers[listenerGuid];
this._quotesProvider.getQuotes(updateType === 1 /* Fast */ ? subscriptionRecord.fastSymbols : subscriptionRecord.symbols)
.then((data) => {
this._requestsPending--;
if (!this._subscribers.hasOwnProperty(listenerGuid)) {
return;
}
subscriptionRecord.listener(data);
logMessage("QuotesPulseProvider: data for #" + listenerGuid + " (" + updateType + ") updated successfully, pending=" + _this._requestsPending);
logMessage(`QuotesPulseProvider: data for #${listenerGuid} (${updateType}) updated successfully, pending=${this._requestsPending}`);
})
.catch(function (reason) {
_this._requestsPending--;
logMessage("QuotesPulseProvider: data for #" + listenerGuid + " (" + updateType + ") updated with error=" + getErrorMessage(reason) + ", pending=" + _this._requestsPending);
.catch((reason) => {
this._requestsPending--;
logMessage(`QuotesPulseProvider: data for #${listenerGuid} (${updateType}) updated with error=${getErrorMessage(reason)}, pending=${this._requestsPending}`);
});
};
var this_1 = this;
for (var listenerGuid in this._subscribers) {
_loop_1(listenerGuid);
}
};
return QuotesPulseProvider;
}());
export { QuotesPulseProvider };
}
}

View File

@@ -1,30 +1,28 @@
import { logMessage } from './helpers';
var Requester = /** @class */ (function () {
function Requester(headers) {
export class Requester {
constructor(headers) {
if (headers) {
this._headers = headers;
}
}
Requester.prototype.sendRequest = function (datafeedUrl, urlPath, params) {
sendRequest(datafeedUrl, urlPath, params) {
if (params !== undefined) {
var paramKeys = Object.keys(params);
const paramKeys = Object.keys(params);
if (paramKeys.length !== 0) {
urlPath += '?';
}
urlPath += paramKeys.map(function (key) {
return encodeURIComponent(key) + "=" + encodeURIComponent(params[key].toString());
urlPath += paramKeys.map((key) => {
return `${encodeURIComponent(key)}=${encodeURIComponent(params[key].toString())}`;
}).join('&');
}
logMessage('New request: ' + urlPath);
// Send user cookies if the URL is on the same origin as the calling script.
var options = { credentials: 'same-origin' };
const options = { credentials: 'same-origin' };
if (this._headers !== undefined) {
options.headers = this._headers;
}
return fetch(datafeedUrl + "/" + urlPath, options)
.then(function (response) { return response.text(); })
.then(function (responseTest) { return JSON.parse(responseTest); });
};
return Requester;
}());
export { Requester };
return fetch(`${datafeedUrl}/${urlPath}`, options)
.then((response) => response.text())
.then((responseTest) => JSON.parse(responseTest));
}
}

View File

@@ -1,17 +1,17 @@
import { getErrorMessage, logMessage, } from './helpers';
function extractField(data, field, arrayIndex, valueIsArray) {
var value = data[field];
const value = data[field];
if (Array.isArray(value) && (!valueIsArray || Array.isArray(value[0]))) {
return value[arrayIndex];
}
return value;
}
function symbolWithCurrencyKey(symbol, currency) {
function symbolKey(symbol, currency, unit) {
// here we're using a separator that quite possible shouldn't be in a real symbol name
return symbol + (currency !== undefined ? '_%|#|%_' + currency : '');
return symbol + (currency !== undefined ? '_%|#|%_' + currency : '') + (unit !== undefined ? '_%|#|%_' + unit : '');
}
var SymbolsStorage = /** @class */ (function () {
function SymbolsStorage(datafeedUrl, datafeedSupportedResolutions, requester) {
export class SymbolsStorage {
constructor(datafeedUrl, datafeedSupportedResolutions, requester) {
this._exchangesList = ['NYSE', 'FOREX', 'AMEX'];
this._symbolsInfo = {};
this._symbolsList = [];
@@ -19,59 +19,53 @@ var SymbolsStorage = /** @class */ (function () {
this._datafeedSupportedResolutions = datafeedSupportedResolutions;
this._requester = requester;
this._readyPromise = this._init();
this._readyPromise.catch(function (error) {
this._readyPromise.catch((error) => {
// seems it is impossible
// tslint:disable-next-line:no-console
console.error("SymbolsStorage: Cannot init, error=" + error.toString());
console.error(`SymbolsStorage: Cannot init, error=${error.toString()}`);
});
}
// BEWARE: this function does not consider symbol's exchange
SymbolsStorage.prototype.resolveSymbol = function (symbolName, currencyCode) {
var _this = this;
return this._readyPromise.then(function () {
var symbolInfo = _this._symbolsInfo[symbolWithCurrencyKey(symbolName, currencyCode)];
resolveSymbol(symbolName, currencyCode, unitId) {
return this._readyPromise.then(() => {
const symbolInfo = this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)];
if (symbolInfo === undefined) {
return Promise.reject('invalid symbol');
}
return Promise.resolve(symbolInfo);
});
};
SymbolsStorage.prototype.searchSymbols = function (searchString, exchange, symbolType, maxSearchResults) {
var _this = this;
return this._readyPromise.then(function () {
var weightedResult = [];
var queryIsEmpty = searchString.length === 0;
}
searchSymbols(searchString, exchange, symbolType, maxSearchResults) {
return this._readyPromise.then(() => {
const weightedResult = [];
const queryIsEmpty = searchString.length === 0;
searchString = searchString.toUpperCase();
var _loop_1 = function (symbolName) {
var symbolInfo = _this._symbolsInfo[symbolName];
for (const symbolName of this._symbolsList) {
const symbolInfo = this._symbolsInfo[symbolName];
if (symbolInfo === undefined) {
return "continue";
continue;
}
if (symbolType.length > 0 && symbolInfo.type !== symbolType) {
return "continue";
continue;
}
if (exchange && exchange.length > 0 && symbolInfo.exchange !== exchange) {
return "continue";
continue;
}
var positionInName = symbolInfo.name.toUpperCase().indexOf(searchString);
var positionInDescription = symbolInfo.description.toUpperCase().indexOf(searchString);
const positionInName = symbolInfo.name.toUpperCase().indexOf(searchString);
const positionInDescription = symbolInfo.description.toUpperCase().indexOf(searchString);
if (queryIsEmpty || positionInName >= 0 || positionInDescription >= 0) {
var alreadyExists = weightedResult.some(function (item) { return item.symbolInfo === symbolInfo; });
const alreadyExists = weightedResult.some((item) => item.symbolInfo === symbolInfo);
if (!alreadyExists) {
var weight = positionInName >= 0 ? positionInName : 8000 + positionInDescription;
const weight = positionInName >= 0 ? positionInName : 8000 + positionInDescription;
weightedResult.push({ symbolInfo: symbolInfo, weight: weight });
}
}
};
for (var _i = 0, _a = _this._symbolsList; _i < _a.length; _i++) {
var symbolName = _a[_i];
_loop_1(symbolName);
}
var result = weightedResult
.sort(function (item1, item2) { return item1.weight - item2.weight; })
const result = weightedResult
.sort((item1, item2) => item1.weight - item2.weight)
.slice(0, maxSearchResults)
.map(function (item) {
var symbolInfo = item.symbolInfo;
.map((item) => {
const symbolInfo = item.symbolInfo;
return {
symbol: symbolInfo.name,
full_name: symbolInfo.full_name,
@@ -84,13 +78,11 @@ var SymbolsStorage = /** @class */ (function () {
});
return Promise.resolve(result);
});
};
SymbolsStorage.prototype._init = function () {
var _this = this;
var promises = [];
var alreadyRequestedExchanges = {};
for (var _i = 0, _a = this._exchangesList; _i < _a.length; _i++) {
var exchange = _a[_i];
}
_init() {
const promises = [];
const alreadyRequestedExchanges = {};
for (const exchange of this._exchangesList) {
if (alreadyRequestedExchanges[exchange]) {
continue;
}
@@ -98,18 +90,17 @@ var SymbolsStorage = /** @class */ (function () {
promises.push(this._requestExchangeData(exchange));
}
return Promise.all(promises)
.then(function () {
_this._symbolsList.sort();
.then(() => {
this._symbolsList.sort();
logMessage('SymbolsStorage: All exchanges data loaded');
});
};
SymbolsStorage.prototype._requestExchangeData = function (exchange) {
var _this = this;
return new Promise(function (resolve, reject) {
_this._requester.sendRequest(_this._datafeedUrl, 'symbol_info', { group: exchange })
.then(function (response) {
}
_requestExchangeData(exchange) {
return new Promise((resolve, reject) => {
this._requester.sendRequest(this._datafeedUrl, 'symbol_info', { group: exchange })
.then((response) => {
try {
_this._onExchangeDataReceived(exchange, response);
this._onExchangeDataReceived(exchange, response);
}
catch (error) {
reject(error);
@@ -117,25 +108,26 @@ var SymbolsStorage = /** @class */ (function () {
}
resolve();
})
.catch(function (reason) {
logMessage("SymbolsStorage: Request data for exchange '" + exchange + "' failed, reason=" + getErrorMessage(reason));
.catch((reason) => {
logMessage(`SymbolsStorage: Request data for exchange '${exchange}' failed, reason=${getErrorMessage(reason)}`);
resolve();
});
});
};
SymbolsStorage.prototype._onExchangeDataReceived = function (exchange, data) {
var symbolIndex = 0;
}
_onExchangeDataReceived(exchange, data) {
let symbolIndex = 0;
try {
var symbolsCount = data.symbol.length;
var tickerPresent = data.ticker !== undefined;
const symbolsCount = data.symbol.length;
const tickerPresent = data.ticker !== undefined;
for (; symbolIndex < symbolsCount; ++symbolIndex) {
var symbolName = data.symbol[symbolIndex];
var listedExchange = extractField(data, 'exchange-listed', symbolIndex);
var tradedExchange = extractField(data, 'exchange-traded', symbolIndex);
var fullName = tradedExchange + ':' + symbolName;
var currencyCode = extractField(data, 'currency-code', symbolIndex);
var ticker = tickerPresent ? extractField(data, 'ticker', symbolIndex) : symbolName;
var symbolInfo = {
const symbolName = data.symbol[symbolIndex];
const listedExchange = extractField(data, 'exchange-listed', symbolIndex);
const tradedExchange = extractField(data, 'exchange-traded', symbolIndex);
const fullName = tradedExchange + ':' + symbolName;
const currencyCode = extractField(data, 'currency-code', symbolIndex);
const unitId = extractField(data, 'unit-id', symbolIndex);
const ticker = tickerPresent ? extractField(data, 'ticker', symbolIndex) : symbolName;
const symbolInfo = {
ticker: ticker,
name: symbolName,
base_name: [listedExchange + ':' + symbolName],
@@ -144,6 +136,9 @@ var SymbolsStorage = /** @class */ (function () {
exchange: tradedExchange,
currency_code: currencyCode,
original_currency_code: extractField(data, 'original-currency-code', symbolIndex),
unit_id: unitId,
original_unit_id: extractField(data, 'original-unit-id', symbolIndex),
unit_conversion_types: extractField(data, 'unit-conversion-types', symbolIndex, true),
description: extractField(data, 'description', symbolIndex),
has_intraday: definedValueOrDefault(extractField(data, 'has-intraday', symbolIndex), false),
has_no_volume: definedValueOrDefault(extractField(data, 'has-no-volume', symbolIndex), false),
@@ -165,21 +160,19 @@ var SymbolsStorage = /** @class */ (function () {
this._symbolsInfo[ticker] = symbolInfo;
this._symbolsInfo[symbolName] = symbolInfo;
this._symbolsInfo[fullName] = symbolInfo;
if (currencyCode !== undefined) {
this._symbolsInfo[symbolWithCurrencyKey(ticker, currencyCode)] = symbolInfo;
this._symbolsInfo[symbolWithCurrencyKey(symbolName, currencyCode)] = symbolInfo;
this._symbolsInfo[symbolWithCurrencyKey(fullName, currencyCode)] = symbolInfo;
if (currencyCode !== undefined || unitId !== undefined) {
this._symbolsInfo[symbolKey(ticker, currencyCode, unitId)] = symbolInfo;
this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)] = symbolInfo;
this._symbolsInfo[symbolKey(fullName, currencyCode, unitId)] = symbolInfo;
}
this._symbolsList.push(symbolName);
}
}
catch (error) {
throw new Error("SymbolsStorage: API error when processing exchange " + exchange + " symbol #" + symbolIndex + " (" + data.symbol[symbolIndex] + "): " + error.message);
throw new Error(`SymbolsStorage: API error when processing exchange ${exchange} symbol #${symbolIndex} (${data.symbol[symbolIndex]}): ${error.message}`);
}
};
return SymbolsStorage;
}());
export { SymbolsStorage };
}
}
function definedValueOrDefault(value, defaultValue) {
return value !== undefined ? value : defaultValue;
}

View File

@@ -4,17 +4,15 @@ import { DataPulseProvider } from './data-pulse-provider';
import { QuotesPulseProvider } from './quotes-pulse-provider';
import { SymbolsStorage } from './symbols-storage';
function extractField(data, field, arrayIndex) {
var value = data[field];
const value = data[field];
return Array.isArray(value) ? value[arrayIndex] : value;
}
/**
* This class implements interaction with UDF-compatible datafeed.
* See UDF protocol reference at https://github.com/tradingview/charting_library/wiki/UDF
*/
var UDFCompatibleDatafeedBase = /** @class */ (function () {
function UDFCompatibleDatafeedBase(datafeedURL, quotesProvider, requester, updateFrequency) {
var _this = this;
if (updateFrequency === void 0) { updateFrequency = 10 * 1000; }
export class UDFCompatibleDatafeedBase {
constructor(datafeedURL, quotesProvider, requester, updateFrequency = 10 * 1000) {
this._configuration = defaultConfiguration();
this._symbolsStorage = null;
this._datafeedURL = datafeedURL;
@@ -24,43 +22,42 @@ var UDFCompatibleDatafeedBase = /** @class */ (function () {
this._dataPulseProvider = new DataPulseProvider(this._historyProvider, updateFrequency);
this._quotesPulseProvider = new QuotesPulseProvider(this._quotesProvider);
this._configurationReadyPromise = this._requestConfiguration()
.then(function (configuration) {
.then((configuration) => {
if (configuration === null) {
configuration = defaultConfiguration();
}
_this._setupWithConfiguration(configuration);
this._setupWithConfiguration(configuration);
});
}
UDFCompatibleDatafeedBase.prototype.onReady = function (callback) {
var _this = this;
this._configurationReadyPromise.then(function () {
callback(_this._configuration);
onReady(callback) {
this._configurationReadyPromise.then(() => {
callback(this._configuration);
});
};
UDFCompatibleDatafeedBase.prototype.getQuotes = function (symbols, onDataCallback, onErrorCallback) {
}
getQuotes(symbols, onDataCallback, onErrorCallback) {
this._quotesProvider.getQuotes(symbols).then(onDataCallback).catch(onErrorCallback);
};
UDFCompatibleDatafeedBase.prototype.subscribeQuotes = function (symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
}
subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
this._quotesPulseProvider.subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid);
};
UDFCompatibleDatafeedBase.prototype.unsubscribeQuotes = function (listenerGuid) {
}
unsubscribeQuotes(listenerGuid) {
this._quotesPulseProvider.unsubscribeQuotes(listenerGuid);
};
UDFCompatibleDatafeedBase.prototype.getMarks = function (symbolInfo, from, to, onDataCallback, resolution) {
}
getMarks(symbolInfo, from, to, onDataCallback, resolution) {
if (!this._configuration.supports_marks) {
return;
}
var requestParams = {
const requestParams = {
symbol: symbolInfo.ticker || '',
from: from,
to: to,
resolution: resolution,
};
this._send('marks', requestParams)
.then(function (response) {
.then((response) => {
if (!Array.isArray(response)) {
var result = [];
for (var i = 0; i < response.id.length; ++i) {
const result = [];
for (let i = 0; i < response.id.length; ++i) {
result.push({
id: extractField(response, 'id', i),
time: extractField(response, 'time', i),
@@ -75,26 +72,26 @@ var UDFCompatibleDatafeedBase = /** @class */ (function () {
}
onDataCallback(response);
})
.catch(function (error) {
logMessage("UdfCompatibleDatafeed: Request marks failed: " + getErrorMessage(error));
.catch((error) => {
logMessage(`UdfCompatibleDatafeed: Request marks failed: ${getErrorMessage(error)}`);
onDataCallback([]);
});
};
UDFCompatibleDatafeedBase.prototype.getTimescaleMarks = function (symbolInfo, from, to, onDataCallback, resolution) {
}
getTimescaleMarks(symbolInfo, from, to, onDataCallback, resolution) {
if (!this._configuration.supports_timescale_marks) {
return;
}
var requestParams = {
const requestParams = {
symbol: symbolInfo.ticker || '',
from: from,
to: to,
resolution: resolution,
};
this._send('timescale_marks', requestParams)
.then(function (response) {
.then((response) => {
if (!Array.isArray(response)) {
var result = [];
for (var i = 0; i < response.id.length; ++i) {
const result = [];
for (let i = 0; i < response.id.length; ++i) {
result.push({
id: extractField(response, 'id', i),
time: extractField(response, 'time', i),
@@ -107,45 +104,45 @@ var UDFCompatibleDatafeedBase = /** @class */ (function () {
}
onDataCallback(response);
})
.catch(function (error) {
logMessage("UdfCompatibleDatafeed: Request timescale marks failed: " + getErrorMessage(error));
.catch((error) => {
logMessage(`UdfCompatibleDatafeed: Request timescale marks failed: ${getErrorMessage(error)}`);
onDataCallback([]);
});
};
UDFCompatibleDatafeedBase.prototype.getServerTime = function (callback) {
}
getServerTime(callback) {
if (!this._configuration.supports_time) {
return;
}
this._send('time')
.then(function (response) {
var time = parseInt(response);
.then((response) => {
const time = parseInt(response);
if (!isNaN(time)) {
callback(time);
}
})
.catch(function (error) {
logMessage("UdfCompatibleDatafeed: Fail to load server time, error=" + getErrorMessage(error));
.catch((error) => {
logMessage(`UdfCompatibleDatafeed: Fail to load server time, error=${getErrorMessage(error)}`);
});
};
UDFCompatibleDatafeedBase.prototype.searchSymbols = function (userInput, exchange, symbolType, onResult) {
}
searchSymbols(userInput, exchange, symbolType, onResult) {
if (this._configuration.supports_search) {
var params = {
const params = {
limit: 30 /* SearchItemsLimit */,
query: userInput.toUpperCase(),
type: symbolType,
exchange: exchange,
};
this._send('search', params)
.then(function (response) {
.then((response) => {
if (response.s !== undefined) {
logMessage("UdfCompatibleDatafeed: search symbols error=" + response.errmsg);
logMessage(`UdfCompatibleDatafeed: search symbols error=${response.errmsg}`);
onResult([]);
return;
}
onResult(response);
})
.catch(function (reason) {
logMessage("UdfCompatibleDatafeed: Search symbols for '" + userInput + "' failed. Error=" + getErrorMessage(reason));
.catch((reason) => {
logMessage(`UdfCompatibleDatafeed: Search symbols for '${userInput}' failed. Error=${getErrorMessage(reason)}`);
onResult([]);
});
}
@@ -157,24 +154,28 @@ var UDFCompatibleDatafeedBase = /** @class */ (function () {
.then(onResult)
.catch(onResult.bind(null, []));
}
};
UDFCompatibleDatafeedBase.prototype.resolveSymbol = function (symbolName, onResolve, onError, extension) {
}
resolveSymbol(symbolName, onResolve, onError, extension) {
logMessage('Resolve requested');
var currencyCode = extension && extension.currencyCode;
var resolveRequestStartTime = Date.now();
const currencyCode = extension && extension.currencyCode;
const unitId = extension && extension.unitId;
const resolveRequestStartTime = Date.now();
function onResultReady(symbolInfo) {
logMessage("Symbol resolved: " + (Date.now() - resolveRequestStartTime) + "ms");
logMessage(`Symbol resolved: ${Date.now() - resolveRequestStartTime}ms`);
onResolve(symbolInfo);
}
if (!this._configuration.supports_group_request) {
var params = {
const params = {
symbol: symbolName,
};
if (currencyCode !== undefined) {
params.currencyCode = currencyCode;
}
if (unitId !== undefined) {
params.unitId = unitId;
}
this._send('symbols', params)
.then(function (response) {
.then((response) => {
if (response.s !== undefined) {
onError('unknown_symbol');
}
@@ -182,8 +183,8 @@ var UDFCompatibleDatafeedBase = /** @class */ (function () {
onResultReady(response);
}
})
.catch(function (reason) {
logMessage("UdfCompatibleDatafeed: Error resolving symbol: " + getErrorMessage(reason));
.catch((reason) => {
logMessage(`UdfCompatibleDatafeed: Error resolving symbol: ${getErrorMessage(reason)}`);
onError('unknown_symbol');
});
}
@@ -191,33 +192,33 @@ var UDFCompatibleDatafeedBase = /** @class */ (function () {
if (this._symbolsStorage === null) {
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
}
this._symbolsStorage.resolveSymbol(symbolName, currencyCode).then(onResultReady).catch(onError);
this._symbolsStorage.resolveSymbol(symbolName, currencyCode, unitId).then(onResultReady).catch(onError);
}
};
UDFCompatibleDatafeedBase.prototype.getBars = function (symbolInfo, resolution, periodParams, onResult, onError) {
}
getBars(symbolInfo, resolution, periodParams, onResult, onError) {
this._historyProvider.getBars(symbolInfo, resolution, periodParams)
.then(function (result) {
.then((result) => {
onResult(result.bars, result.meta);
})
.catch(onError);
};
UDFCompatibleDatafeedBase.prototype.subscribeBars = function (symbolInfo, resolution, onTick, listenerGuid, onResetCacheNeededCallback) {
}
subscribeBars(symbolInfo, resolution, onTick, listenerGuid, onResetCacheNeededCallback) {
this._dataPulseProvider.subscribeBars(symbolInfo, resolution, onTick, listenerGuid);
};
UDFCompatibleDatafeedBase.prototype.unsubscribeBars = function (listenerGuid) {
}
unsubscribeBars(listenerGuid) {
this._dataPulseProvider.unsubscribeBars(listenerGuid);
};
UDFCompatibleDatafeedBase.prototype._requestConfiguration = function () {
}
_requestConfiguration() {
return this._send('config')
.catch(function (reason) {
logMessage("UdfCompatibleDatafeed: Cannot get datafeed configuration - use default, error=" + getErrorMessage(reason));
.catch((reason) => {
logMessage(`UdfCompatibleDatafeed: Cannot get datafeed configuration - use default, error=${getErrorMessage(reason)}`);
return null;
});
};
UDFCompatibleDatafeedBase.prototype._send = function (urlPath, params) {
}
_send(urlPath, params) {
return this._requester.sendRequest(this._datafeedURL, urlPath, params);
};
UDFCompatibleDatafeedBase.prototype._setupWithConfiguration = function (configurationData) {
}
_setupWithConfiguration(configurationData) {
this._configuration = configurationData;
if (configurationData.exchanges === undefined) {
configurationData.exchanges = [];
@@ -228,11 +229,9 @@ var UDFCompatibleDatafeedBase = /** @class */ (function () {
if (configurationData.supports_group_request || !configurationData.supports_search) {
this._symbolsStorage = new SymbolsStorage(this._datafeedURL, configurationData.supported_resolutions || [], this._requester);
}
logMessage("UdfCompatibleDatafeed: Initialized with " + JSON.stringify(configurationData));
};
return UDFCompatibleDatafeedBase;
}());
export { UDFCompatibleDatafeedBase };
logMessage(`UdfCompatibleDatafeed: Initialized with ${JSON.stringify(configurationData)}`);
}
}
function defaultConfiguration() {
return {
supports_search: false,

View File

@@ -1,17 +1,10 @@
import { __extends } from "tslib";
import { UDFCompatibleDatafeedBase } from './udf-compatible-datafeed-base';
import { QuotesProvider } from './quotes-provider';
import { Requester } from './requester';
var UDFCompatibleDatafeed = /** @class */ (function (_super) {
__extends(UDFCompatibleDatafeed, _super);
function UDFCompatibleDatafeed(datafeedURL, updateFrequency) {
if (updateFrequency === void 0) { updateFrequency = 10 * 1000; }
var _this = this;
var requester = new Requester();
var quotesProvider = new QuotesProvider(datafeedURL, requester);
_this = _super.call(this, datafeedURL, quotesProvider, requester, updateFrequency) || this;
return _this;
export class UDFCompatibleDatafeed extends UDFCompatibleDatafeedBase {
constructor(datafeedURL, updateFrequency = 10 * 1000) {
const requester = new Requester();
const quotesProvider = new QuotesProvider(datafeedURL, requester);
super(datafeedURL, quotesProvider, requester, updateFrequency);
}
return UDFCompatibleDatafeed;
}(UDFCompatibleDatafeedBase));
export { UDFCompatibleDatafeed };
}

View File

@@ -1,16 +1,13 @@
{
"private": true,
"dependencies": {
"promise-polyfill": "6.0.2",
"tslib": "2.0.3",
"whatwg-fetch": "2.0.3"
"tslib": "2.1.0"
},
"devDependencies": {
"@rollup/plugin-buble": "~0.21.3",
"@rollup/plugin-node-resolve": "~9.0.0",
"rollup": "~2.28.2",
"rollup-plugin-uglify": "~6.0.4",
"typescript": "4.1.2"
"rollup-plugin-terser": "~7.0.2",
"typescript": "4.2.3"
},
"scripts": {
"compile": "tsc",

View File

@@ -1,7 +1,6 @@
/* globals process */
import buble from '@rollup/plugin-buble';
import { uglify } from 'rollup-plugin-uglify';
import { terser } from 'rollup-plugin-terser';
import { nodeResolve } from '@rollup/plugin-node-resolve';
const environment = process.env.ENV || 'development';
@@ -17,21 +16,11 @@ export default [
},
plugins: [
nodeResolve(),
buble(),
!isDevelopmentEnv && uglify({ output: { inline_script: true } }),
],
},
{
input: 'src/polyfills.es6',
context: 'window',
output: {
format: 'iife',
file: 'dist/polyfills.js',
},
plugins: [
nodeResolve(),
buble(),
uglify({ output: { inline_script: true } }),
!isDevelopmentEnv && terser({
ecma: 2017,
safari10: true,
output: { inline_script: true },
}),
],
},
];

View File

@@ -71,6 +71,10 @@ export class HistoryProvider {
requestParams.currencyCode = symbolInfo.currency_code;
}
if (symbolInfo.unit_id !== undefined) {
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) => {

View File

@@ -1,2 +0,0 @@
import 'promise-polyfill';
import 'whatwg-fetch';

View File

@@ -47,6 +47,9 @@ interface ExchangeDataResponseSymbolData {
'has-no-volume'?: boolean;
'currency-code'?: string;
'original-currency-code'?: string;
'unit-id'?: string;
'original-unit-id'?: string;
'unit-conversion-types'?: string[];
'volume-precision'?: number;
}
@@ -80,9 +83,9 @@ function extractField<Field extends keyof ExchangeDataResponseSymbolData>(data:
return value as ExchangeDataResponseSymbolData[Field];
}
function symbolWithCurrencyKey(symbol: string, currency?: string): string {
function symbolKey(symbol: string, currency?: string, unit?: string): string {
// here we're using a separator that quite possible shouldn't be in a real symbol name
return symbol + (currency !== undefined ? '_%|#|%_' + currency : '');
return symbol + (currency !== undefined ? '_%|#|%_' + currency : '') + (unit !== undefined ? '_%|#|%_' + unit : '');
}
export class SymbolsStorage {
@@ -107,9 +110,9 @@ export class SymbolsStorage {
}
// BEWARE: this function does not consider symbol's exchange
public resolveSymbol(symbolName: string, currencyCode?: string): Promise<LibrarySymbolInfo> {
public resolveSymbol(symbolName: string, currencyCode?: string, unitId?: string): Promise<LibrarySymbolInfo> {
return this._readyPromise.then(() => {
const symbolInfo = this._symbolsInfo[symbolWithCurrencyKey(symbolName, currencyCode)];
const symbolInfo = this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)];
if (symbolInfo === undefined) {
return Promise.reject('invalid symbol');
}
@@ -234,6 +237,7 @@ export class SymbolsStorage {
const tradedExchange = extractField(data, 'exchange-traded', symbolIndex);
const fullName = tradedExchange + ':' + symbolName;
const currencyCode = extractField(data, 'currency-code', symbolIndex);
const unitId = extractField(data, 'unit-id', symbolIndex);
const ticker = tickerPresent ? (extractField(data, 'ticker', symbolIndex) as string) : symbolName;
@@ -246,6 +250,9 @@ export class SymbolsStorage {
exchange: tradedExchange,
currency_code: currencyCode,
original_currency_code: extractField(data, 'original-currency-code', symbolIndex),
unit_id: unitId,
original_unit_id: extractField(data, 'original-unit-id', symbolIndex),
unit_conversion_types: extractField(data, 'unit-conversion-types', symbolIndex, true),
description: extractField(data, 'description', symbolIndex),
has_intraday: definedValueOrDefault(extractField(data, 'has-intraday', symbolIndex), false),
has_no_volume: definedValueOrDefault(extractField(data, 'has-no-volume', symbolIndex), false),
@@ -268,10 +275,10 @@ export class SymbolsStorage {
this._symbolsInfo[ticker] = symbolInfo;
this._symbolsInfo[symbolName] = symbolInfo;
this._symbolsInfo[fullName] = symbolInfo;
if (currencyCode !== undefined) {
this._symbolsInfo[symbolWithCurrencyKey(ticker, currencyCode)] = symbolInfo;
this._symbolsInfo[symbolWithCurrencyKey(symbolName, currencyCode)] = symbolInfo;
this._symbolsInfo[symbolWithCurrencyKey(fullName, currencyCode)] = symbolInfo;
if (currencyCode !== undefined || unitId !== undefined) {
this._symbolsInfo[symbolKey(ticker, currencyCode, unitId)] = symbolInfo;
this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)] = symbolInfo;
this._symbolsInfo[symbolKey(fullName, currencyCode, unitId)] = symbolInfo;
}
this._symbolsList.push(symbolName);

View File

@@ -262,6 +262,7 @@ export class UDFCompatibleDatafeedBase implements IExternalDatafeed, IDatafeedQu
logMessage('Resolve requested');
const currencyCode = extension && extension.currencyCode;
const unitId = extension && extension.unitId;
const resolveRequestStartTime = Date.now();
function onResultReady(symbolInfo: LibrarySymbolInfo): void {
@@ -276,6 +277,9 @@ export class UDFCompatibleDatafeedBase implements IExternalDatafeed, IDatafeedQu
if (currencyCode !== undefined) {
params.currencyCode = currencyCode;
}
if (unitId !== undefined) {
params.unitId = unitId;
}
this._send<ResolveSymbolResponse | UdfErrorResponse>('symbols', params)
.then((response: ResolveSymbolResponse | UdfErrorResponse) => {
@@ -294,7 +298,7 @@ export class UDFCompatibleDatafeedBase implements IExternalDatafeed, IDatafeedQu
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
}
this._symbolsStorage.resolveSymbol(symbolName, currencyCode).then(onResultReady).catch(onError);
this._symbolsStorage.resolveSymbol(symbolName, currencyCode, unitId).then(onResultReady).catch(onError);
}
}

View File

@@ -4,10 +4,7 @@
"importHelpers": true,
"lib": [
"dom",
"es2015.promise",
"es2015.iterable",
"es2015.symbol.wellknown",
"es5"
"es2017"
],
"module": "es6",
"moduleResolution": "node",
@@ -19,7 +16,7 @@
"rootDir": "src",
"sourceMap": false,
"strict": true,
"target": "es5",
"target": "es2017",
"types": []
},
"include": [