VERSION 1.14 @ 2019-04-04 08:32:01.655131

x axis retains previous scale after restoring layout #3828
getVisiblePriceRange() not working in 1.14 (unstable) #3774
uppercase_instrument_names doesn't work for compare/overlay #3746
API method to trigger the Zoom Out #3739
Provide event that study properties was changed #3736
Fixed security issue in master #3699
widget.save have old data #3623
JS error on switching chart types #3602
Add a way to prevent scrolling beyond the beginning of bars #3597
Chart customization - overrides #3572
Theme override colors from saved_data #3557
Error while applying Pivot Points Indicator #3521
Month timeframe combining months #3510
initialSettings from settings adapter does not work #3479
Add Indexed to 100 mode for price scale #3391
RTL #3335
Add warning about incorrect usage disable_resolution_rebuild and has_empty_bars #3329
Unable to load DOME and Bottom Widget when open_account_manager disabled #3312
Randomly clickable area on chart that redirects user to tradingview.com #3299
Apply to all #3298
Save container element while charting library's life #3297
Gap appearing in chart layout #3246
User's colors not working when theme is enabled #3232
Chart is outside of visible range when in auto scale and load study template or load chart #3229
Please make getVisibleRange return the range in UTC #3173
Remove TradingView.onready from API #3162
Add API to export data from the chart #3152
Market status is not updated properly #3122
Placeholders disappear in "Compare or Add Symbol" popup when the cursor is set into the fields #2966
getVisibleRange to return 0, any way of getting the last bar in active chart ? #2757
"onListAdded" event is not fired when user click on "Save Watchlist As" #2654
Error report : Parabolic SAR #2205
TERMINAL: make a flag to disable support of stop orders #2181
Add Change % in the OHLC label Charting Library #2120
Request more bars for indicators #1362
Drawing tool does not work on Samsung Edge and  Note 5 #984
This commit is contained in:
Jenkins
2019-04-04 11:32:07 +03:00
parent e38d004695
commit 9d8b63e068
142 changed files with 38224 additions and 2420 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,7 +1,10 @@
import { getErrorMessage, logMessage, } from './helpers';
function extractField(data, field, arrayIndex) {
function extractField(data, field, arrayIndex, valueIsArray) {
var value = data[field];
return Array.isArray(value) ? value[arrayIndex] : value;
if (Array.isArray(value) && (!valueIsArray || Array.isArray(value[0]))) {
return value[arrayIndex];
}
return value;
}
var SymbolsStorage = /** @class */ (function () {
function SymbolsStorage(datafeedUrl, datafeedSupportedResolutions, requester) {
@@ -143,10 +146,10 @@ var SymbolsStorage = /** @class */ (function () {
type: extractField(data, 'type', symbolIndex),
session: extractField(data, 'session-regular', symbolIndex),
timezone: extractField(data, 'timezone', symbolIndex),
supported_resolutions: definedValueOrDefault(extractField(data, 'supported-resolutions', symbolIndex), this._datafeedSupportedResolutions),
supported_resolutions: definedValueOrDefault(extractField(data, 'supported-resolutions', symbolIndex, true), this._datafeedSupportedResolutions),
force_session_rebuild: extractField(data, 'force-session-rebuild', symbolIndex),
has_daily: definedValueOrDefault(extractField(data, 'has-daily', symbolIndex), true),
intraday_multipliers: definedValueOrDefault(extractField(data, 'intraday-multipliers', symbolIndex), ['1', '5', '15', '30', '60']),
intraday_multipliers: definedValueOrDefault(extractField(data, 'intraday-multipliers', symbolIndex, true), ['1', '5', '15', '30', '60']),
has_weekly_and_monthly: extractField(data, 'has-weekly-and-monthly', symbolIndex),
has_empty_bars: extractField(data, 'has-empty-bars', symbolIndex),
volume_precision: definedValueOrDefault(extractField(data, 'volume-precision', symbolIndex), 0),

View File

@@ -10,7 +10,7 @@
"rollup-plugin-buble": "0.15.0",
"rollup-plugin-node-resolve": "3.0.0",
"rollup-plugin-uglify": "2.0.1",
"typescript": "2.7.1"
"typescript": "3.0.1"
},
"scripts": {
"compile": "tsc",

View File

@@ -0,0 +1,107 @@
import { getErrorMessage, logMessage, } from './helpers';
var DataPulseProvider = /** @class */ (function () {
function DataPulseProvider(historyProvider, updateFrequency) {
this._subscribers = {};
this._requestsPending = 0;
this._historyProvider = historyProvider;
setInterval(this._updateData.bind(this), updateFrequency);
}
DataPulseProvider.prototype.subscribeBars = function (symbolInfo, resolution, newDataCallback, listenerGuid) {
if (this._subscribers.hasOwnProperty(listenerGuid)) {
logMessage("DataPulseProvider: already has subscriber with id=" + listenerGuid);
return;
}
this._subscribers[listenerGuid] = {
lastBarTime: null,
listener: newDataCallback,
resolution: resolution,
symbolInfo: symbolInfo,
};
logMessage("DataPulseProvider: subscribed for #" + listenerGuid + " - {" + symbolInfo.name + ", " + resolution + "}");
};
DataPulseProvider.prototype.unsubscribeBars = function (listenerGuid) {
delete this._subscribers[listenerGuid];
logMessage("DataPulseProvider: unsubscribed for #" + listenerGuid);
};
DataPulseProvider.prototype._updateData = function () {
var _this = this;
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);
})
.catch(function (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());
// 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);
return this._historyProvider.getBars(subscriptionRecord.symbolInfo, subscriptionRecord.resolution, rangeStartTime, rangeEndTime)
.then(function (result) {
_this._onSubscriberDataReceived(listenerGuid, result);
});
};
DataPulseProvider.prototype._onSubscriberDataReceived = function (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);
return;
}
var bars = result.bars;
if (bars.length === 0) {
return;
}
var lastBar = bars[bars.length - 1];
var subscriptionRecord = this._subscribers[listenerGuid];
if (subscriptionRecord.lastBarTime !== null && lastBar.time < subscriptionRecord.lastBarTime) {
return;
}
var 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];
subscriptionRecord.listener(previousBar);
}
subscriptionRecord.lastBarTime = lastBar.time;
subscriptionRecord.listener(lastBar);
};
return DataPulseProvider;
}());
export { DataPulseProvider };
function periodLengthSeconds(resolution, requiredPeriodsCount) {
var daysCount = 0;
if (resolution === 'D' || resolution === '1D') {
daysCount = requiredPeriodsCount;
}
else if (resolution === 'M' || resolution === '1M') {
daysCount = 31 * requiredPeriodsCount;
}
else if (resolution === 'W' || resolution === '1W') {
daysCount = 7 * requiredPeriodsCount;
}
else {
daysCount = requiredPeriodsCount * parseInt(resolution) / (24 * 60);
}
return daysCount * 24 * 60 * 60;
}

View File

@@ -0,0 +1,19 @@
/**
* If you want to enable logs from datafeed set it to `true`
*/
var isLoggingEnabled = false;
export function logMessage(message) {
if (isLoggingEnabled) {
var now = new Date();
console.log(now.toLocaleTimeString() + "." + now.getMilliseconds() + "> " + message);
}
}
export function getErrorMessage(error) {
if (error === undefined) {
return '';
}
else if (typeof error === 'string') {
return error;
}
return error.message;
}

View File

@@ -0,0 +1,66 @@
import { getErrorMessage, } from './helpers';
var HistoryProvider = /** @class */ (function () {
function HistoryProvider(datafeedUrl, requester) {
this._datafeedUrl = datafeedUrl;
this._requester = requester;
}
HistoryProvider.prototype.getBars = function (symbolInfo, resolution, rangeStartDate, rangeEndDate) {
var _this = this;
var requestParams = {
symbol: symbolInfo.ticker || '',
resolution: resolution,
from: rangeStartDate,
to: rangeEndDate,
};
return new Promise(function (resolve, reject) {
_this._requester.sendRequest(_this._datafeedUrl, 'history', requestParams)
.then(function (response) {
if (response.s !== 'ok' && response.s !== 'no_data') {
reject(response.errmsg);
return;
}
var bars = [];
var meta = {
noData: false,
};
if (response.s === 'no_data') {
meta.noData = true;
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 = {
time: response.t[i] * 1000,
close: Number(response.c[i]),
open: Number(response.c[i]),
high: Number(response.c[i]),
low: Number(response.c[i]),
};
if (ohlPresent) {
barValue.open = Number(response.o[i]);
barValue.high = Number(response.h[i]);
barValue.low = Number(response.l[i]);
}
if (volumePresent) {
barValue.volume = Number(response.v[i]);
}
bars.push(barValue);
}
}
resolve({
bars: bars,
meta: meta,
});
})
.catch(function (reason) {
var reasonString = getErrorMessage(reason);
console.warn("HistoryProvider: getBars() failed, error=" + reasonString);
reject(reasonString);
});
});
};
return HistoryProvider;
}());
export { HistoryProvider };

View File

View File

@@ -0,0 +1,28 @@
import { getErrorMessage, logMessage, } from './helpers';
var QuotesProvider = /** @class */ (function () {
function QuotesProvider(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) {
if (response.s === 'ok') {
resolve(response.d);
}
else {
reject(response.errmsg);
}
})
.catch(function (error) {
var errorMessage = getErrorMessage(error);
logMessage("QuotesProvider: getQuotes failed, error=" + errorMessage);
reject("network error: " + errorMessage);
});
});
};
return QuotesProvider;
}());
export { QuotesProvider };

View File

@@ -0,0 +1,51 @@
import { getErrorMessage, logMessage, } from './helpers';
var QuotesPulseProvider = /** @class */ (function () {
function QuotesPulseProvider(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) {
this._subscribers[listenerGuid] = {
symbols: symbols,
fastSymbols: fastSymbols,
listener: onRealtimeCallback,
};
logMessage("QuotesPulseProvider: subscribed quotes with #" + listenerGuid);
};
QuotesPulseProvider.prototype.unsubscribeQuotes = function (listenerGuid) {
delete this._subscribers[listenerGuid];
logMessage("QuotesPulseProvider: unsubscribed quotes with #" + listenerGuid);
};
QuotesPulseProvider.prototype._updateQuotes = function (updateType) {
var _this = this;
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)) {
return;
}
subscriptionRecord.listener(data);
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);
});
};
var this_1 = this;
for (var listenerGuid in this._subscribers) {
_loop_1(listenerGuid);
}
};
return QuotesPulseProvider;
}());
export { QuotesPulseProvider };

View File

@@ -0,0 +1,30 @@
import { logMessage } from './helpers';
var Requester = /** @class */ (function () {
function Requester(headers) {
if (headers) {
this._headers = headers;
}
}
Requester.prototype.sendRequest = function (datafeedUrl, urlPath, params) {
if (params !== undefined) {
var paramKeys = Object.keys(params);
if (paramKeys.length !== 0) {
urlPath += '?';
}
urlPath += paramKeys.map(function (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' };
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 };

View File

@@ -0,0 +1,172 @@
import { getErrorMessage, logMessage, } from './helpers';
function extractField(data, field, arrayIndex, valueIsArray) {
var value = data[field];
if (Array.isArray(value) && (!valueIsArray || Array.isArray(value[0]))) {
return value[arrayIndex];
}
return value;
}
var SymbolsStorage = /** @class */ (function () {
function SymbolsStorage(datafeedUrl, datafeedSupportedResolutions, requester) {
this._exchangesList = ['NYSE', 'FOREX', 'AMEX'];
this._symbolsInfo = {};
this._symbolsList = [];
this._datafeedUrl = datafeedUrl;
this._datafeedSupportedResolutions = datafeedSupportedResolutions;
this._requester = requester;
this._readyPromise = this._init();
this._readyPromise.catch(function (error) {
// seems it is impossible
console.error("SymbolsStorage: Cannot init, error=" + error.toString());
});
}
// BEWARE: this function does not consider symbol's exchange
SymbolsStorage.prototype.resolveSymbol = function (symbolName) {
var _this = this;
return this._readyPromise.then(function () {
var symbolInfo = _this._symbolsInfo[symbolName];
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;
searchString = searchString.toUpperCase();
var _loop_1 = function (symbolName) {
var symbolInfo = _this._symbolsInfo[symbolName];
if (symbolInfo === undefined) {
return "continue";
}
if (symbolType.length > 0 && symbolInfo.type !== symbolType) {
return "continue";
}
if (exchange && exchange.length > 0 && symbolInfo.exchange !== exchange) {
return "continue";
}
var positionInName = symbolInfo.name.toUpperCase().indexOf(searchString);
var positionInDescription = symbolInfo.description.toUpperCase().indexOf(searchString);
if (queryIsEmpty || positionInName >= 0 || positionInDescription >= 0) {
var alreadyExists = weightedResult.some(function (item) { return item.symbolInfo === symbolInfo; });
if (!alreadyExists) {
var 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; })
.slice(0, maxSearchResults)
.map(function (item) {
var symbolInfo = item.symbolInfo;
return {
symbol: symbolInfo.name,
full_name: symbolInfo.full_name,
description: symbolInfo.description,
exchange: symbolInfo.exchange,
params: [],
type: symbolInfo.type,
ticker: symbolInfo.name,
};
});
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];
if (alreadyRequestedExchanges[exchange]) {
continue;
}
alreadyRequestedExchanges[exchange] = true;
promises.push(this._requestExchangeData(exchange));
}
return Promise.all(promises)
.then(function () {
_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) {
try {
_this._onExchangeDataReceived(exchange, response);
}
catch (error) {
reject(error);
return;
}
resolve();
})
.catch(function (reason) {
logMessage("SymbolsStorage: Request data for exchange '" + exchange + "' failed, reason=" + getErrorMessage(reason));
resolve();
});
});
};
SymbolsStorage.prototype._onExchangeDataReceived = function (exchange, data) {
var symbolIndex = 0;
try {
var symbolsCount = data.symbol.length;
var 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 ticker = tickerPresent ? extractField(data, 'ticker', symbolIndex) : symbolName;
var symbolInfo = {
ticker: ticker,
name: symbolName,
base_name: [listedExchange + ':' + symbolName],
full_name: fullName,
listed_exchange: listedExchange,
exchange: tradedExchange,
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),
minmov: extractField(data, 'minmovement', symbolIndex) || extractField(data, 'minmov', symbolIndex) || 0,
minmove2: extractField(data, 'minmove2', symbolIndex) || extractField(data, 'minmov2', symbolIndex),
fractional: extractField(data, 'fractional', symbolIndex),
pricescale: extractField(data, 'pricescale', symbolIndex),
type: extractField(data, 'type', symbolIndex),
session: extractField(data, 'session-regular', symbolIndex),
timezone: extractField(data, 'timezone', symbolIndex),
supported_resolutions: definedValueOrDefault(extractField(data, 'supported-resolutions', symbolIndex, true), this._datafeedSupportedResolutions),
force_session_rebuild: extractField(data, 'force-session-rebuild', symbolIndex),
has_daily: definedValueOrDefault(extractField(data, 'has-daily', symbolIndex), true),
intraday_multipliers: definedValueOrDefault(extractField(data, 'intraday-multipliers', symbolIndex, true), ['1', '5', '15', '30', '60']),
has_weekly_and_monthly: extractField(data, 'has-weekly-and-monthly', symbolIndex),
has_empty_bars: extractField(data, 'has-empty-bars', symbolIndex),
volume_precision: definedValueOrDefault(extractField(data, 'volume-precision', symbolIndex), 0),
};
this._symbolsInfo[ticker] = symbolInfo;
this._symbolsInfo[symbolName] = symbolInfo;
this._symbolsInfo[fullName] = symbolInfo;
this._symbolsList.push(symbolName);
}
}
catch (error) {
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

@@ -14,31 +14,7 @@ interface SymbolInfoMap {
[symbol: string]: LibrarySymbolInfo | undefined;
}
interface ExchangeDataResponseOptionalValues {
'ticker': string;
'minmov2': number;
'minmove2': number;
'minmov': number;
'minmovement': number;
'supported-resolutions': string[];
'force-session-rebuild': boolean;
'has-intraday': boolean;
'has-daily': boolean;
'has-weekly-and-monthly': boolean;
'has-empty-bars': boolean;
'has-no-volume': boolean;
'intraday-multipliers': string[];
'volume-precision': number;
}
interface ExchangeDataResponseMandatoryValues {
interface ExchangeDataResponseSymbolData {
'type': string;
'timezone': LibrarySymbolInfo['timezone'];
'description': string;
@@ -51,26 +27,56 @@ interface ExchangeDataResponseMandatoryValues {
'fractional': boolean;
'pricescale': number;
'ticker'?: string;
'minmov2'?: number;
'minmove2'?: number;
'minmov'?: number;
'minmovement'?: number;
'force-session-rebuild'?: boolean;
'supported-resolutions'?: string[];
'intraday-multipliers'?: string[];
'has-intraday'?: boolean;
'has-daily'?: boolean;
'has-weekly-and-monthly'?: boolean;
'has-empty-bars'?: boolean;
'has-no-volume'?: boolean;
'volume-precision'?: number;
}
// Here is some black magic with types to get compile-time checks of names and types
type ValueOrArray<T> = T | T[];
type PickArrayedObjectFields<T> = Pick<T, {
// tslint:disable-next-line:no-any
[K in keyof T]-?: NonNullable<T[K]> extends any[] ? K : never;
}[keyof T]>;
type ExchangeDataResponseArrayedSymbolData = PickArrayedObjectFields<ExchangeDataResponseSymbolData>;
type ExchangeDataResponseNonArrayedSymbolData = Pick<ExchangeDataResponseSymbolData, Exclude<keyof ExchangeDataResponseSymbolData, keyof ExchangeDataResponseArrayedSymbolData>>;
type ExchangeDataResponse =
{
symbol: string[];
} &
{
[K in keyof ExchangeDataResponseMandatoryValues]: ValueOrArray<ExchangeDataResponseMandatoryValues[K]>;
} &
{
[K in keyof ExchangeDataResponseOptionalValues]?: ValueOrArray<ExchangeDataResponseOptionalValues[K]>;
[K in keyof ExchangeDataResponseSymbolData]: ExchangeDataResponseSymbolData[K] | NonNullable<ExchangeDataResponseSymbolData[K]>[];
};
function extractField<Field extends keyof ExchangeDataResponseMandatoryValues>(data: ExchangeDataResponse, field: Field, arrayIndex: number): ExchangeDataResponseMandatoryValues[Field];
function extractField<Field extends keyof ExchangeDataResponseOptionalValues>(data: ExchangeDataResponse, field: Field, arrayIndex: number): ExchangeDataResponseOptionalValues[Field] | undefined;
function extractField<Field extends keyof ExchangeDataResponseMandatoryValues>(data: ExchangeDataResponse, field: Field, arrayIndex: number): (ExchangeDataResponseMandatoryValues & ExchangeDataResponseOptionalValues)[Field] | undefined {
const value = data[field];
return Array.isArray(value) ? value[arrayIndex] : value;
function extractField<Field extends keyof ExchangeDataResponseNonArrayedSymbolData>(data: ExchangeDataResponse, field: Field, arrayIndex: number): ExchangeDataResponseNonArrayedSymbolData[Field];
function extractField<Field extends keyof ExchangeDataResponseArrayedSymbolData>(data: ExchangeDataResponse, field: Field, arrayIndex: number, valueIsArray: true): ExchangeDataResponseArrayedSymbolData[Field];
function extractField<Field extends keyof ExchangeDataResponseSymbolData>(data: ExchangeDataResponse, field: Field, arrayIndex: number, valueIsArray?: boolean): ExchangeDataResponseSymbolData[Field] {
const value: ExchangeDataResponse[keyof ExchangeDataResponseSymbolData] = data[field];
if (Array.isArray(value) && (!valueIsArray || Array.isArray(value[0]))) {
return value[arrayIndex];
}
return value as ExchangeDataResponseSymbolData[Field];
}
export class SymbolsStorage {
@@ -240,10 +246,10 @@ export class SymbolsStorage {
type: extractField(data, 'type', symbolIndex),
session: extractField(data, 'session-regular', symbolIndex),
timezone: extractField(data, 'timezone', symbolIndex),
supported_resolutions: definedValueOrDefault(extractField(data, 'supported-resolutions', symbolIndex), this._datafeedSupportedResolutions),
supported_resolutions: definedValueOrDefault(extractField(data, 'supported-resolutions', symbolIndex, true), this._datafeedSupportedResolutions),
force_session_rebuild: extractField(data, 'force-session-rebuild', symbolIndex),
has_daily: definedValueOrDefault(extractField(data, 'has-daily', symbolIndex), true),
intraday_multipliers: definedValueOrDefault(extractField(data, 'intraday-multipliers', symbolIndex), ['1', '5', '15', '30', '60']),
intraday_multipliers: definedValueOrDefault(extractField(data, 'intraday-multipliers', symbolIndex, true), ['1', '5', '15', '30', '60']),
has_weekly_and_monthly: extractField(data, 'has-weekly-and-monthly', symbolIndex),
has_empty_bars: extractField(data, 'has-empty-bars', symbolIndex),
volume_precision: definedValueOrDefault(extractField(data, 'volume-precision', symbolIndex), 0),

View File

@@ -0,0 +1,243 @@
import { getErrorMessage, logMessage, } from './helpers';
import { HistoryProvider, } from './history-provider';
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];
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) {
if (updateFrequency === void 0) { updateFrequency = 10 * 1000; }
var _this = this;
this._configuration = defaultConfiguration();
this._symbolsStorage = null;
this._datafeedURL = datafeedURL;
this._requester = requester;
this._historyProvider = new HistoryProvider(datafeedURL, this._requester);
this._quotesProvider = quotesProvider;
this._dataPulseProvider = new DataPulseProvider(this._historyProvider, updateFrequency);
this._quotesPulseProvider = new QuotesPulseProvider(this._quotesProvider);
this._configurationReadyPromise = this._requestConfiguration()
.then(function (configuration) {
if (configuration === null) {
configuration = defaultConfiguration();
}
_this._setupWithConfiguration(configuration);
});
}
UDFCompatibleDatafeedBase.prototype.onReady = function (callback) {
var _this = this;
this._configurationReadyPromise.then(function () {
callback(_this._configuration);
});
};
UDFCompatibleDatafeedBase.prototype.getQuotes = function (symbols, onDataCallback, onErrorCallback) {
this._quotesProvider.getQuotes(symbols).then(onDataCallback).catch(onErrorCallback);
};
UDFCompatibleDatafeedBase.prototype.subscribeQuotes = function (symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
this._quotesPulseProvider.subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid);
};
UDFCompatibleDatafeedBase.prototype.unsubscribeQuotes = function (listenerGuid) {
this._quotesPulseProvider.unsubscribeQuotes(listenerGuid);
};
UDFCompatibleDatafeedBase.prototype.calculateHistoryDepth = function (resolution, resolutionBack, intervalBack) {
return undefined;
};
UDFCompatibleDatafeedBase.prototype.getMarks = function (symbolInfo, from, to, onDataCallback, resolution) {
if (!this._configuration.supports_marks) {
return;
}
var requestParams = {
symbol: symbolInfo.ticker || '',
from: from,
to: to,
resolution: resolution,
};
this._send('marks', requestParams)
.then(function (response) {
if (!Array.isArray(response)) {
var result = [];
for (var i = 0; i < response.id.length; ++i) {
result.push({
id: extractField(response, 'id', i),
time: extractField(response, 'time', i),
color: extractField(response, 'color', i),
text: extractField(response, 'text', i),
label: extractField(response, 'label', i),
labelFontColor: extractField(response, 'labelFontColor', i),
minSize: extractField(response, 'minSize', i),
});
}
response = result;
}
onDataCallback(response);
})
.catch(function (error) {
logMessage("UdfCompatibleDatafeed: Request marks failed: " + getErrorMessage(error));
onDataCallback([]);
});
};
UDFCompatibleDatafeedBase.prototype.getTimescaleMarks = function (symbolInfo, from, to, onDataCallback, resolution) {
if (!this._configuration.supports_timescale_marks) {
return;
}
var requestParams = {
symbol: symbolInfo.ticker || '',
from: from,
to: to,
resolution: resolution,
};
this._send('timescale_marks', requestParams)
.then(function (response) {
if (!Array.isArray(response)) {
var result = [];
for (var i = 0; i < response.id.length; ++i) {
result.push({
id: extractField(response, 'id', i),
time: extractField(response, 'time', i),
color: extractField(response, 'color', i),
label: extractField(response, 'label', i),
tooltip: extractField(response, 'tooltip', i),
});
}
response = result;
}
onDataCallback(response);
})
.catch(function (error) {
logMessage("UdfCompatibleDatafeed: Request timescale marks failed: " + getErrorMessage(error));
onDataCallback([]);
});
};
UDFCompatibleDatafeedBase.prototype.getServerTime = function (callback) {
if (!this._configuration.supports_time) {
return;
}
this._send('time')
.then(function (response) {
var time = parseInt(response);
if (!isNaN(time)) {
callback(time);
}
})
.catch(function (error) {
logMessage("UdfCompatibleDatafeed: Fail to load server time, error=" + getErrorMessage(error));
});
};
UDFCompatibleDatafeedBase.prototype.searchSymbols = function (userInput, exchange, symbolType, onResult) {
if (this._configuration.supports_search) {
var params = {
limit: 30 /* SearchItemsLimit */,
query: userInput.toUpperCase(),
type: symbolType,
exchange: exchange,
};
this._send('search', params)
.then(function (response) {
if (response.s !== undefined) {
logMessage("UdfCompatibleDatafeed: search symbols error=" + response.errmsg);
onResult([]);
return;
}
onResult(response);
})
.catch(function (reason) {
logMessage("UdfCompatibleDatafeed: Search symbols for '" + userInput + "' failed. Error=" + getErrorMessage(reason));
onResult([]);
});
}
else {
if (this._symbolsStorage === null) {
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
}
this._symbolsStorage.searchSymbols(userInput, exchange, symbolType, 30 /* SearchItemsLimit */)
.then(onResult)
.catch(onResult.bind(null, []));
}
};
UDFCompatibleDatafeedBase.prototype.resolveSymbol = function (symbolName, onResolve, onError) {
logMessage('Resolve requested');
var resolveRequestStartTime = Date.now();
function onResultReady(symbolInfo) {
logMessage("Symbol resolved: " + (Date.now() - resolveRequestStartTime) + "ms");
onResolve(symbolInfo);
}
if (!this._configuration.supports_group_request) {
var params = {
symbol: symbolName,
};
this._send('symbols', params)
.then(function (response) {
if (response.s !== undefined) {
onError('unknown_symbol');
}
else {
onResultReady(response);
}
})
.catch(function (reason) {
logMessage("UdfCompatibleDatafeed: Error resolving symbol: " + getErrorMessage(reason));
onError('unknown_symbol');
});
}
else {
if (this._symbolsStorage === null) {
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
}
this._symbolsStorage.resolveSymbol(symbolName).then(onResultReady).catch(onError);
}
};
UDFCompatibleDatafeedBase.prototype.getBars = function (symbolInfo, resolution, rangeStartDate, rangeEndDate, onResult, onError) {
this._historyProvider.getBars(symbolInfo, resolution, rangeStartDate, rangeEndDate)
.then(function (result) {
onResult(result.bars, result.meta);
})
.catch(onError);
};
UDFCompatibleDatafeedBase.prototype.subscribeBars = function (symbolInfo, resolution, onTick, listenerGuid, onResetCacheNeededCallback) {
this._dataPulseProvider.subscribeBars(symbolInfo, resolution, onTick, listenerGuid);
};
UDFCompatibleDatafeedBase.prototype.unsubscribeBars = function (listenerGuid) {
this._dataPulseProvider.unsubscribeBars(listenerGuid);
};
UDFCompatibleDatafeedBase.prototype._requestConfiguration = function () {
return this._send('config')
.catch(function (reason) {
logMessage("UdfCompatibleDatafeed: Cannot get datafeed configuration - use default, error=" + getErrorMessage(reason));
return null;
});
};
UDFCompatibleDatafeedBase.prototype._send = function (urlPath, params) {
return this._requester.sendRequest(this._datafeedURL, urlPath, params);
};
UDFCompatibleDatafeedBase.prototype._setupWithConfiguration = function (configurationData) {
this._configuration = configurationData;
if (configurationData.exchanges === undefined) {
configurationData.exchanges = [];
}
if (!configurationData.supports_search && !configurationData.supports_group_request) {
throw new Error('Unsupported datafeed configuration. Must either support search, or support group request');
}
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 };
function defaultConfiguration() {
return {
supports_search: false,
supports_group_request: true,
supported_resolutions: ['1', '5', '15', '30', '60', '1D', '1W', '1M'],
supports_marks: false,
supports_timescale_marks: false,
};
}

View File

@@ -40,10 +40,10 @@ import { SymbolsStorage } from './symbols-storage';
import { Requester } from './requester';
export interface UdfCompatibleConfiguration extends DatafeedConfiguration {
// tslint:disable
// tslint:disable:tv-variable-name
supports_search?: boolean;
supports_group_request?: boolean;
// tslint:enable
// tslint:enable:tv-variable-name
}
export interface ResolveSymbolResponse extends LibrarySymbolInfo {

View File

@@ -0,0 +1,17 @@
import * as tslib_1 from "tslib";
import { UDFCompatibleDatafeedBase } from './udf-compatible-datafeed-base';
import { QuotesProvider } from './quotes-provider';
import { Requester } from './requester';
var UDFCompatibleDatafeed = /** @class */ (function (_super) {
tslib_1.__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;
}
return UDFCompatibleDatafeed;
}(UDFCompatibleDatafeedBase));
export { UDFCompatibleDatafeed };