| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840 |
- 'use strict';
- /*
- This class implements interaction with UDF-compatible datafeed.
- See UDF protocol reference at
- https://github.com/tradingview/charting_library/wiki/UDF
- */
- function parseJSONorNot(mayBeJSON) {
- if (typeof mayBeJSON === 'string') {
- return JSON.parse(mayBeJSON);
- } else {
- return mayBeJSON;
- }
- }
- var Datafeeds = {};
- Datafeeds.UDFCompatibleDatafeed = function(datafeedURL, updateFrequency) {
- this._datafeedURL = datafeedURL;
- this._configuration = undefined;
- this._symbolSearch = null;
- this._symbolsStorage = null;
- this._barsPulseUpdater = new Datafeeds.DataPulseUpdater(this, updateFrequency || 10 * 1000);
- this._quotesPulseUpdater = new Datafeeds.QuotesPulseUpdater(this);
- this._enableLogging = false;
- this._initializationFinished = false;
- this._callbacks = {};
- this._initialize();
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.defaultConfiguration = function() {
- 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
- };
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.getServerTime = function(callback) {
- if (this._configuration.supports_time) {
- this._send(this._datafeedURL + '/time', {})
- .done(function(response) {
- callback(+response);
- })
- .fail(function() {
- });
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.on = function(event, callback) {
- if (!this._callbacks.hasOwnProperty(event)) {
- this._callbacks[event] = [];
- }
- this._callbacks[event].push(callback);
- return this;
- };
- Datafeeds.UDFCompatibleDatafeed.prototype._fireEvent = function(event, argument) {
- if (this._callbacks.hasOwnProperty(event)) {
- var callbacksChain = this._callbacks[event];
- for (var i = 0; i < callbacksChain.length; ++i) {
- callbacksChain[i](argument);
- }
- this._callbacks[event] = [];
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.onInitialized = function() {
- this._initializationFinished = true;
- this._fireEvent('initialized');
- };
- Datafeeds.UDFCompatibleDatafeed.prototype._logMessage = function(message) {
- if (this._enableLogging) {
- var now = new Date();
- console.log(now.toLocaleTimeString() + '.' + now.getMilliseconds() + '> ' + message);
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype._send = function(url, params) {
- var request = url;
- if (params) {
- for (var i = 0; i < Object.keys(params).length; ++i) {
- var key = Object.keys(params)[i];
- var value = encodeURIComponent(params[key]);
- request += (i === 0 ? '?' : '&') + key + '=' + value;
- }
- }
- this._logMessage('New request: ' + request);
- return $.ajax({
- type: 'GET',
- url: request,
- contentType: 'text/plain'
- });
- };
- Datafeeds.UDFCompatibleDatafeed.prototype._initialize = function() {
- var that = this;
- this._send(this._datafeedURL + '/config')
- .done(function(response) {
- var configurationData = parseJSONorNot(response);
- that._setupWithConfiguration(configurationData);
- })
- .fail(function(reason) {
- that._setupWithConfiguration(that.defaultConfiguration());
- });
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.onReady = function(callback) {
- var that = this;
- if (this._configuration) {
- setTimeout(function() {
- callback(that._configuration);
- }, 0);
- } else {
- this.on('configuration_ready', function() {
- callback(that._configuration);
- });
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype._setupWithConfiguration = function(configurationData) {
- this._configuration = configurationData;
- if (!configurationData.exchanges) {
- configurationData.exchanges = [];
- }
- // @obsolete; remove in 1.5
- var supportedResolutions = configurationData.supported_resolutions || configurationData.supportedResolutions;
- configurationData.supported_resolutions = supportedResolutions;
- // @obsolete; remove in 1.5
- var symbolsTypes = configurationData.symbols_types || configurationData.symbolsTypes;
- configurationData.symbols_types = symbolsTypes;
- 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_search) {
- this._symbolSearch = new Datafeeds.SymbolSearchComponent(this);
- }
- if (configurationData.supports_group_request) {
- // this component will call onInitialized() by itself
- this._symbolsStorage = new Datafeeds.SymbolsStorage(this);
- } else {
- this.onInitialized();
- }
- this._fireEvent('configuration_ready');
- this._logMessage('Initialized with ' + JSON.stringify(configurationData));
- };
- // ===============================================================================================================================
- // The functions set below is the implementation of JavaScript API.
- Datafeeds.UDFCompatibleDatafeed.prototype.getMarks = function(symbolInfo, rangeStart, rangeEnd, onDataCallback, resolution) {
- if (this._configuration.supports_marks) {
- this._send(this._datafeedURL + '/marks', {
- symbol: symbolInfo.ticker.toUpperCase(),
- from: rangeStart,
- to: rangeEnd,
- resolution: resolution
- })
- .done(function(response) {
- onDataCallback(parseJSONorNot(response));
- })
- .fail(function() {
- onDataCallback([]);
- });
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.getTimescaleMarks = function(symbolInfo, rangeStart, rangeEnd, onDataCallback, resolution) {
- if (this._configuration.supports_timescale_marks) {
- this._send(this._datafeedURL + '/timescale_marks', {
- symbol: symbolInfo.ticker.toUpperCase(),
- from: rangeStart,
- to: rangeEnd,
- resolution: resolution
- })
- .done(function(response) {
- onDataCallback(parseJSONorNot(response));
- })
- .fail(function() {
- onDataCallback([]);
- });
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.searchSymbols = function(searchString, exchange, type, onResultReadyCallback) {
- var MAX_SEARCH_RESULTS = 30;
- if (!this._configuration) {
- onResultReadyCallback([]);
- return;
- }
- if (this._configuration.supports_search) {
- this._send(this._datafeedURL + '/search', {
- limit: MAX_SEARCH_RESULTS,
- query: searchString.toUpperCase(),
- type: type,
- exchange: exchange
- })
- .done(function(response) {
- var data = parseJSONorNot(response);
- for (var i = 0; i < data.length; ++i) {
- if (!data[i].params) {
- data[i].params = [];
- }
- data[i].exchange = data[i].exchange || '';
- }
- if (typeof data.s == 'undefined' || data.s !== 'error') {
- onResultReadyCallback(data);
- } else {
- onResultReadyCallback([]);
- }
- })
- .fail(function(reason) {
- onResultReadyCallback([]);
- });
- } else {
- if (!this._symbolSearch) {
- throw new Error('Datafeed error: inconsistent configuration (symbol search)');
- }
- var searchArgument = {
- searchString: searchString,
- exchange: exchange,
- type: type,
- onResultReadyCallback: onResultReadyCallback
- };
- if (this._initializationFinished) {
- this._symbolSearch.searchSymbols(searchArgument, MAX_SEARCH_RESULTS);
- } else {
- var that = this;
- this.on('initialized', function() {
- that._symbolSearch.searchSymbols(searchArgument, MAX_SEARCH_RESULTS);
- });
- }
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype._symbolResolveURL = '/symbols';
- // BEWARE: this function does not consider symbol's exchange
- Datafeeds.UDFCompatibleDatafeed.prototype.resolveSymbol = function(symbolName, onSymbolResolvedCallback, onResolveErrorCallback) {
- var that = this;
- if (!this._initializationFinished) {
- this.on('initialized', function() {
- that.resolveSymbol(symbolName, onSymbolResolvedCallback, onResolveErrorCallback);
- });
- return;
- }
- var resolveRequestStartTime = Date.now();
- that._logMessage('Resolve requested');
- function onResultReady(data) {
- var postProcessedData = data;
- if (that.postProcessSymbolInfo) {
- postProcessedData = that.postProcessSymbolInfo(postProcessedData);
- }
- that._logMessage('Symbol resolved: ' + (Date.now() - resolveRequestStartTime));
- onSymbolResolvedCallback(postProcessedData);
- }
- if (!this._configuration.supports_group_request) {
- this._send(this._datafeedURL + this._symbolResolveURL, {
- symbol: symbolName ? symbolName.toUpperCase() : ''
- })
- .done(function(response) {
- var data = parseJSONorNot(response);
- if (data.s && data.s !== 'ok') {
- onResolveErrorCallback('unknown_symbol');
- } else {
- onResultReady(data);
- }
- })
- .fail(function(reason) {
- that._logMessage('Error resolving symbol: ' + JSON.stringify([reason]));
- onResolveErrorCallback('unknown_symbol');
- });
- } else {
- if (this._initializationFinished) {
- this._symbolsStorage.resolveSymbol(symbolName, onResultReady, onResolveErrorCallback);
- } else {
- this.on('initialized', function() {
- that._symbolsStorage.resolveSymbol(symbolName, onResultReady, onResolveErrorCallback);
- });
- }
- }
- };
- Datafeeds.UDFCompatibleDatafeed.prototype._historyURL = '/history';
- Datafeeds.UDFCompatibleDatafeed.prototype.getBars = function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onDataCallback, onErrorCallback) {
- // timestamp sample: 1399939200
- if (rangeStartDate > 0 && (rangeStartDate + '').length > 10) {
- throw new Error(['Got a JS time instead of Unix one.', rangeStartDate, rangeEndDate]);
- }
- this._send(this._datafeedURL + this._historyURL, {
- symbol: symbolInfo.ticker.toUpperCase(),
- resolution: resolution,
- from: rangeStartDate,
- to: rangeEndDate
- })
- .done(function(response) {
- var data = parseJSONorNot(response);
- var nodata = data.s === 'no_data';
- if (data.s !== 'ok' && !nodata) {
- if (!!onErrorCallback) {
- onErrorCallback(data.s);
- }
- return;
- }
- var bars = [];
- // data is JSON having format {s: "status" (ok, no_data, error),
- // v: [volumes], t: [times], o: [opens], h: [highs], l: [lows], c:[closes], nb: "optional_unixtime_if_no_data"}
- var barsCount = nodata ? 0 : data.t.length;
- var volumePresent = typeof data.v != 'undefined';
- var ohlPresent = typeof data.o != 'undefined';
- for (var i = 0; i < barsCount; ++i) {
- var barValue = {
- time: data.t[i] * 1000,
- close: data.c[i]
- };
- if (ohlPresent) {
- barValue.open = data.o[i];
- barValue.high = data.h[i];
- barValue.low = data.l[i];
- } else {
- barValue.open = barValue.high = barValue.low = barValue.close;
- }
- if (volumePresent) {
- barValue.volume = data.v[i];
- }
- bars.push(barValue);
- }
- onDataCallback(bars, { noData: nodata, nextTime: data.nb || data.nextTime });
- })
- .fail(function(arg) {
- console.warn(['getBars(): HTTP error', arg]);
- if (!!onErrorCallback) {
- onErrorCallback('network error: ' + JSON.stringify(arg));
- }
- });
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.subscribeBars = function(symbolInfo, resolution, onRealtimeCallback, listenerGUID, onResetCacheNeededCallback) {
- this._barsPulseUpdater.subscribeDataListener(symbolInfo, resolution, onRealtimeCallback, listenerGUID, onResetCacheNeededCallback);
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.unsubscribeBars = function(listenerGUID) {
- this._barsPulseUpdater.unsubscribeDataListener(listenerGUID);
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.calculateHistoryDepth = function(period, resolutionBack, intervalBack) {
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.getQuotes = function(symbols, onDataCallback, onErrorCallback) {
- this._send(this._datafeedURL + '/quotes', { symbols: symbols })
- .done(function(response) {
- var data = parseJSONorNot(response);
- if (data.s === 'ok') {
- // JSON format is {s: "status", [{s: "symbol_status", n: "symbol_name", v: {"field1": "value1", "field2": "value2", ..., "fieldN": "valueN"}}]}
- if (onDataCallback) {
- onDataCallback(data.d);
- }
- } else {
- if (onErrorCallback) {
- onErrorCallback(data.errmsg);
- }
- }
- })
- .fail(function(arg) {
- if (onErrorCallback) {
- onErrorCallback('network error: ' + arg);
- }
- });
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.subscribeQuotes = function(symbols, fastSymbols, onRealtimeCallback, listenerGUID) {
- this._quotesPulseUpdater.subscribeDataListener(symbols, fastSymbols, onRealtimeCallback, listenerGUID);
- };
- Datafeeds.UDFCompatibleDatafeed.prototype.unsubscribeQuotes = function(listenerGUID) {
- this._quotesPulseUpdater.unsubscribeDataListener(listenerGUID);
- };
- // ==================================================================================================================================================
- // ==================================================================================================================================================
- // ==================================================================================================================================================
- /*
- It's a symbol storage component for ExternalDatafeed. This component can
- * interact to UDF-compatible datafeed which supports whole group info requesting
- * do symbol resolving -- return symbol info by its name
- */
- Datafeeds.SymbolsStorage = function(datafeed) {
- this._datafeed = datafeed;
- this._exchangesList = ['NYSE', 'FOREX', 'AMEX'];
- this._exchangesWaitingForData = {};
- this._exchangesDataCache = {};
- this._symbolsInfo = {};
- this._symbolsList = [];
- this._requestFullSymbolsList();
- };
- Datafeeds.SymbolsStorage.prototype._requestFullSymbolsList = function() {
- var that = this;
- for (var i = 0; i < this._exchangesList.length; ++i) {
- var exchange = this._exchangesList[i];
- if (this._exchangesDataCache.hasOwnProperty(exchange)) {
- continue;
- }
- this._exchangesDataCache[exchange] = true;
- this._exchangesWaitingForData[exchange] = 'waiting_for_data';
- this._datafeed._send(this._datafeed._datafeedURL + '/symbol_info', {
- group: exchange
- })
- .done((function(exchange) {
- return function(response) {
- that._onExchangeDataReceived(exchange, parseJSONorNot(response));
- that._onAnyExchangeResponseReceived(exchange);
- };
- })(exchange))
- .fail((function(exchange) {
- return function(reason) {
- that._onAnyExchangeResponseReceived(exchange);
- };
- })(exchange));
- }
- };
- Datafeeds.SymbolsStorage.prototype._onExchangeDataReceived = function(exchangeName, data) {
- function tableField(data, name, index) {
- return data[name] instanceof Array ?
- data[name][index] :
- data[name];
- }
- try {
- for (var symbolIndex = 0; symbolIndex < data.symbol.length; ++symbolIndex) {
- var symbolName = data.symbol[symbolIndex];
- var listedExchange = tableField(data, 'exchange-listed', symbolIndex);
- var tradedExchange = tableField(data, 'exchange-traded', symbolIndex);
- var fullName = tradedExchange + ':' + symbolName;
- // This feature support is not implemented yet
- // var hasDWM = tableField(data, "has-dwm", symbolIndex);
- var hasIntraday = tableField(data, 'has-intraday', symbolIndex);
- var tickerPresent = typeof data.ticker != 'undefined';
- var symbolInfo = {
- name: symbolName,
- base_name: [listedExchange + ':' + symbolName],
- description: tableField(data, 'description', symbolIndex),
- full_name: fullName,
- legs: [fullName],
- has_intraday: hasIntraday,
- has_no_volume: tableField(data, 'has-no-volume', symbolIndex),
- listed_exchange: listedExchange,
- exchange: tradedExchange,
- minmov: tableField(data, 'minmovement', symbolIndex) || tableField(data, 'minmov', symbolIndex),
- minmove2: tableField(data, 'minmove2', symbolIndex) || tableField(data, 'minmov2', symbolIndex),
- fractional: tableField(data, 'fractional', symbolIndex),
- pointvalue: tableField(data, 'pointvalue', symbolIndex),
- pricescale: tableField(data, 'pricescale', symbolIndex),
- type: tableField(data, 'type', symbolIndex),
- session: tableField(data, 'session-regular', symbolIndex),
- ticker: tickerPresent ? tableField(data, 'ticker', symbolIndex) : symbolName,
- timezone: tableField(data, 'timezone', symbolIndex),
- supported_resolutions: tableField(data, 'supported-resolutions', symbolIndex) || this._datafeed.defaultConfiguration().supported_resolutions,
- force_session_rebuild: tableField(data, 'force-session-rebuild', symbolIndex) || false,
- has_daily: tableField(data, 'has-daily', symbolIndex) || true,
- intraday_multipliers: tableField(data, 'intraday-multipliers', symbolIndex) || ['1', '5', '15', '30', '60'],
- has_fractional_volume: tableField(data, 'has-fractional-volume', symbolIndex) || false,
- has_weekly_and_monthly: tableField(data, 'has-weekly-and-monthly', symbolIndex) || false,
- has_empty_bars: tableField(data, 'has-empty-bars', symbolIndex) || false,
- volume_precision: tableField(data, 'volume-precision', symbolIndex) || 0
- };
- this._symbolsInfo[symbolInfo.ticker] = this._symbolsInfo[symbolName] = this._symbolsInfo[fullName] = symbolInfo;
- this._symbolsList.push(symbolName);
- }
- } catch (error) {
- throw new Error('API error when processing exchange `' + exchangeName + '` symbol #' + symbolIndex + ': ' + error);
- }
- };
- Datafeeds.SymbolsStorage.prototype._onAnyExchangeResponseReceived = function(exchangeName) {
- delete this._exchangesWaitingForData[exchangeName];
- var allDataReady = Object.keys(this._exchangesWaitingForData).length === 0;
- if (allDataReady) {
- this._symbolsList.sort();
- this._datafeed._logMessage('All exchanges data ready');
- this._datafeed.onInitialized();
- }
- };
- // BEWARE: this function does not consider symbol's exchange
- Datafeeds.SymbolsStorage.prototype.resolveSymbol = function(symbolName, onSymbolResolvedCallback, onResolveErrorCallback) {
- var that = this;
- setTimeout(function() {
- if (!that._symbolsInfo.hasOwnProperty(symbolName)) {
- onResolveErrorCallback('invalid symbol');
- } else {
- onSymbolResolvedCallback(that._symbolsInfo[symbolName]);
- }
- }, 0);
- };
- // ==================================================================================================================================================
- // ==================================================================================================================================================
- // ==================================================================================================================================================
- /*
- It's a symbol search component for ExternalDatafeed. This component can do symbol search only.
- This component strongly depends on SymbolsDataStorage and cannot work without it. Maybe, it would be
- better to merge it to SymbolsDataStorage.
- */
- Datafeeds.SymbolSearchComponent = function(datafeed) {
- this._datafeed = datafeed;
- };
- // searchArgument = { searchString, onResultReadyCallback}
- Datafeeds.SymbolSearchComponent.prototype.searchSymbols = function(searchArgument, maxSearchResults) {
- if (!this._datafeed._symbolsStorage) {
- throw new Error('Cannot use local symbol search when no groups information is available');
- }
- var symbolsStorage = this._datafeed._symbolsStorage;
- var results = []; // array of WeightedItem { item, weight }
- var queryIsEmpty = !searchArgument.searchString || searchArgument.searchString.length === 0;
- var searchStringUpperCase = searchArgument.searchString.toUpperCase();
- for (var i = 0; i < symbolsStorage._symbolsList.length; ++i) {
- var symbolName = symbolsStorage._symbolsList[i];
- var item = symbolsStorage._symbolsInfo[symbolName];
- if (searchArgument.type && searchArgument.type.length > 0 && item.type !== searchArgument.type) {
- continue;
- }
- if (searchArgument.exchange && searchArgument.exchange.length > 0 && item.exchange !== searchArgument.exchange) {
- continue;
- }
- var positionInName = item.name.toUpperCase().indexOf(searchStringUpperCase);
- var positionInDescription = item.description.toUpperCase().indexOf(searchStringUpperCase);
- if (queryIsEmpty || positionInName >= 0 || positionInDescription >= 0) {
- var found = false;
- for (var resultIndex = 0; resultIndex < results.length; resultIndex++) {
- if (results[resultIndex].item === item) {
- found = true;
- break;
- }
- }
- if (!found) {
- var weight = positionInName >= 0 ? positionInName : 8000 + positionInDescription;
- results.push({ item: item, weight: weight });
- }
- }
- }
- searchArgument.onResultReadyCallback(
- results
- .sort(function(weightedItem1, weightedItem2) {
- return weightedItem1.weight - weightedItem2.weight;
- })
- .map(function(weightedItem) {
- var item = weightedItem.item;
- return {
- symbol: item.name,
- full_name: item.full_name,
- description: item.description,
- exchange: item.exchange,
- params: [],
- type: item.type,
- ticker: item.name
- };
- })
- .slice(0, Math.min(results.length, maxSearchResults))
- );
- };
- // ==================================================================================================================================================
- // ==================================================================================================================================================
- // ==================================================================================================================================================
- /*
- This is a pulse updating components for ExternalDatafeed. They emulates realtime updates with periodic requests.
- */
- Datafeeds.DataPulseUpdater = function(datafeed, updateFrequency) {
- this._datafeed = datafeed;
- this._subscribers = {};
- this._requestsPending = 0;
- var that = this;
- var update = function() {
- if (that._requestsPending > 0) {
- return;
- }
- for (var listenerGUID in that._subscribers) {
- var subscriptionRecord = that._subscribers[listenerGUID];
- var resolution = subscriptionRecord.resolution;
- var datesRangeRight = parseInt((new Date().valueOf()) / 1000);
- // 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 datesRangeLeft = datesRangeRight - that.periodLengthSeconds(resolution, 10);
- that._requestsPending++;
- (function(_subscriptionRecord) { // eslint-disable-line
- that._datafeed.getBars(_subscriptionRecord.symbolInfo, resolution, datesRangeLeft, datesRangeRight, function(bars) {
- that._requestsPending--;
- // means the subscription was cancelled while waiting for data
- if (!that._subscribers.hasOwnProperty(listenerGUID)) {
- return;
- }
- if (bars.length === 0) {
- return;
- }
- var lastBar = bars[bars.length - 1];
- if (!isNaN(_subscriptionRecord.lastBarTime) && lastBar.time < _subscriptionRecord.lastBarTime) {
- return;
- }
- var subscribers = _subscriptionRecord.listeners;
- // BEWARE: this one isn't working when first update comes and this update makes a new bar. In this case
- // _subscriptionRecord.lastBarTime = NaN
- var isNewBar = !isNaN(_subscriptionRecord.lastBarTime) && 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];
- for (var i = 0; i < subscribers.length; ++i) {
- subscribers[i](previousBar);
- }
- }
- _subscriptionRecord.lastBarTime = lastBar.time;
- for (var i = 0; i < subscribers.length; ++i) {
- subscribers[i](lastBar);
- }
- },
- // on error
- function() {
- that._requestsPending--;
- });
- })(subscriptionRecord);
- }
- };
- if (typeof updateFrequency != 'undefined' && updateFrequency > 0) {
- setInterval(update, updateFrequency);
- }
- };
- Datafeeds.DataPulseUpdater.prototype.unsubscribeDataListener = function(listenerGUID) {
- this._datafeed._logMessage('Unsubscribing ' + listenerGUID);
- delete this._subscribers[listenerGUID];
- };
- Datafeeds.DataPulseUpdater.prototype.subscribeDataListener = function(symbolInfo, resolution, newDataCallback, listenerGUID) {
- this._datafeed._logMessage('Subscribing ' + listenerGUID);
- if (!this._subscribers.hasOwnProperty(listenerGUID)) {
- this._subscribers[listenerGUID] = {
- symbolInfo: symbolInfo,
- resolution: resolution,
- lastBarTime: NaN,
- listeners: []
- };
- }
- this._subscribers[listenerGUID].listeners.push(newDataCallback);
- };
- Datafeeds.DataPulseUpdater.prototype.periodLengthSeconds = function(resolution, requiredPeriodsCount) {
- var daysCount = 0;
- if (resolution === 'D') {
- daysCount = requiredPeriodsCount;
- } else if (resolution === 'M') {
- daysCount = 31 * requiredPeriodsCount;
- } else if (resolution === 'W') {
- daysCount = 7 * requiredPeriodsCount;
- } else {
- daysCount = requiredPeriodsCount * resolution / (24 * 60);
- }
- return daysCount * 24 * 60 * 60;
- };
- Datafeeds.QuotesPulseUpdater = function(datafeed) {
- this._datafeed = datafeed;
- this._subscribers = {};
- this._updateInterval = 60 * 1000;
- this._fastUpdateInterval = 10 * 1000;
- this._requestsPending = 0;
- var that = this;
- setInterval(function() {
- that._updateQuotes(function(subscriptionRecord) { return subscriptionRecord.symbols; });
- }, this._updateInterval);
- setInterval(function() {
- that._updateQuotes(function(subscriptionRecord) { return subscriptionRecord.fastSymbols.length > 0 ? subscriptionRecord.fastSymbols : subscriptionRecord.symbols; });
- }, this._fastUpdateInterval);
- };
- Datafeeds.QuotesPulseUpdater.prototype.subscribeDataListener = function(symbols, fastSymbols, newDataCallback, listenerGUID) {
- if (!this._subscribers.hasOwnProperty(listenerGUID)) {
- this._subscribers[listenerGUID] = {
- symbols: symbols,
- fastSymbols: fastSymbols,
- listeners: []
- };
- }
- this._subscribers[listenerGUID].listeners.push(newDataCallback);
- };
- Datafeeds.QuotesPulseUpdater.prototype.unsubscribeDataListener = function(listenerGUID) {
- delete this._subscribers[listenerGUID];
- };
- Datafeeds.QuotesPulseUpdater.prototype._updateQuotes = function(symbolsGetter) {
- if (this._requestsPending > 0) {
- return;
- }
- var that = this;
- for (var listenerGUID in this._subscribers) {
- this._requestsPending++;
- var subscriptionRecord = this._subscribers[listenerGUID];
- this._datafeed.getQuotes(symbolsGetter(subscriptionRecord),
- // onDataCallback
- (function(subscribers, guid) { // eslint-disable-line
- return function(data) {
- that._requestsPending--;
- // means the subscription was cancelled while waiting for data
- if (!that._subscribers.hasOwnProperty(guid)) {
- return;
- }
- for (var i = 0; i < subscribers.length; ++i) {
- subscribers[i](data);
- }
- };
- }(subscriptionRecord.listeners, listenerGUID)),
- // onErrorCallback
- function(error) {
- that._requestsPending--;
- });
- }
- };
- if (typeof module !== 'undefined' && module && module.exports) {
- module.exports = {
- UDFCompatibleDatafeed: Datafeeds.UDFCompatibleDatafeed,
- };
- }
|