· 6 years ago · Jan 24, 2020, 03:08 PM
1/*
2 This class implements interaction with UDF-compatible datafeed.
3 Please remember this class is a separate component and may interact to other code through Datafeeds.DatafeedInterface interface functions ONLY
4
5 See UDF protocol reference at
6 https://docs.google.com/document/d/1rAigRhQUSLgLCzUAiVBJGAB7uchb-PzFVe0Bl8WTtF0
7*/
8
9Datafeeds = {};
10
11
12Datafeeds.UDFCompatibleDatafeed = function(datafeedURL, updateFrequency) {
13
14 this._datafeedURL = datafeedURL;
15 this._configuration = undefined;
16
17 this._symbolSearch = null;
18 this._symbolsStorage = null;
19 this._barsPulseUpdater = new Datafeeds.DataPulseUpdater(this, updateFrequency || 10 * 1000);
20 this._quotesPulseUpdater = new Datafeeds.QuotesPulseUpdater(this);
21
22 this._enableLogging = false;
23 this._initializationFinished = false;
24 this._callbacks = {};
25
26 this._initialize();
27};
28
29Datafeeds.UDFCompatibleDatafeed.prototype.defaultConfiguration = function() {
30 return {
31 supports_search: false,
32 supports_group_request: true,
33 supported_resolutions: ["1", "5", "15", "30", "60", "1D", "1W", "1M"],
34 supports_marks: false
35 };
36};
37
38Datafeeds.UDFCompatibleDatafeed.prototype.on = function (event, callback) {
39
40 if (!this._callbacks.hasOwnProperty(event)) {
41 this._callbacks[event] = [];
42 }
43
44 this._callbacks[event].push(callback);
45 return this;
46};
47
48
49Datafeeds.UDFCompatibleDatafeed.prototype._fireEvent = function(event, argument) {
50 if (this._callbacks.hasOwnProperty(event)) {
51 var callbacksChain = this._callbacks[event];
52 for (var i = 0; i < callbacksChain.length; ++i) {
53 callbacksChain[i](argument);
54 }
55 this._callbacks[event] = [];
56 }
57};
58
59Datafeeds.UDFCompatibleDatafeed.prototype.onInitialized = function() {
60 this._initializationFinished = true;
61 this._fireEvent("initialized");
62};
63
64
65
66Datafeeds.UDFCompatibleDatafeed.prototype._logMessage = function(message) {
67 if (this._enableLogging) {
68 var now = new Date();
69 console.log(now.toLocaleTimeString() + "." + now.getMilliseconds() + "> " + message);
70 }
71};
72
73
74Datafeeds.UDFCompatibleDatafeed.prototype._send = function(url, params) {
75 var request = url;
76 if (params) {
77 for (var i = 0; i < Object.keys(params).length; ++i) {
78 var key = Object.keys(params)[i];
79 var value = encodeURIComponent(params[key]);
80 request += (i === 0 ? "?" : "&") + key + "=" + value;
81 }
82 }
83
84 this._logMessage("New request: " + request);
85
86 return $.ajax(request);
87};
88
89Datafeeds.UDFCompatibleDatafeed.prototype._initialize = function() {
90
91 var that = this;
92
93 this._send(this._datafeedURL + "/config")
94 .done(function(response) {
95 var configurationData = JSON.parse(response);
96 that._setupWithConfiguration(configurationData);
97 })
98 .fail(function(reason) {
99 that._setupWithConfiguration(that.defaultConfiguration());
100 });
101};
102
103
104Datafeeds.UDFCompatibleDatafeed.prototype.setup = function(studyEngineOptions, callback) {
105
106 if (this._configuration) {
107 this._configuration.engine = studyEngineOptions;
108 callback(this._configuration);
109 }
110 else {
111 var that = this;
112 this.on("configuration_ready", function() {
113 that._configuration.engine = studyEngineOptions;
114 callback(that._configuration);
115 });
116 }
117};
118
119Datafeeds.UDFCompatibleDatafeed.prototype._setupWithConfiguration = function(configurationData) {
120 this._configuration = configurationData;
121
122 if (!configurationData.exchanges) {
123 configurationData.exchanges = [];
124 }
125
126 // @obsolete; remove in 1.5
127 var supportedResolutions = configurationData.supported_resolutions || configurationData.supportedResolutions;
128 configurationData.supported_resolutions = supportedResolutions;
129
130 // @obsolete; remove in 1.5
131 var symbolsTypes = configurationData.symbols_types || configurationData.symbolsTypes;
132 configurationData.symbols_types = symbolsTypes;
133
134 if (!configurationData.supports_search && !configurationData.supports_group_request) {
135 throw "Unsupported datafeed configuration. Must either support search, or support group request";
136 }
137
138 if (!configurationData.supports_search) {
139 this._symbolSearch = new Datafeeds.SymbolSearchComponent(this);
140 }
141
142 if (configurationData.supports_group_request) {
143 // this component will call onInitialized() by itself
144 this._symbolsStorage = new Datafeeds.SymbolsStorage(this);
145 }
146 else {
147 this.onInitialized();
148 }
149
150 this._fireEvent("configuration_ready");
151 this._logMessage("Initialized with " + JSON.stringify(configurationData));
152};
153
154
155// ===============================================================================================================================
156// The functions set below is the implementation of JavaScript API.
157
158Datafeeds.UDFCompatibleDatafeed.prototype.getMarks = function (symbolInfo, rangeStart, rangeEnd, onDataCallback, resolution) {
159 if (this._configuration.supports_marks) {
160 this._send(this._datafeedURL + "/marks", {
161 symbol: symbolInfo.ticker.toUpperCase(),
162 from : rangeStart,
163 to: rangeEnd,
164 resolution: resolution
165 })
166 .done(function (response) {
167 onDataCallback(JSON.parse(response));
168 })
169 .fail(function() {
170 onDataCallback([]);
171 });
172 }
173};
174
175
176Datafeeds.UDFCompatibleDatafeed.prototype.searchSymbolsByName = function(ticker, exchange, type, onResultReadyCallback) {
177 var MAX_SEARCH_RESULTS = 30;
178
179 if (!this._configuration) {
180 onResultReadyCallback([]);
181 return;
182 }
183
184 if (this._configuration.supports_search) {
185
186 this._send(this._datafeedURL + "/search", {
187 limit: MAX_SEARCH_RESULTS,
188 query: ticker.toUpperCase(),
189 type: type,
190 exchange: exchange
191 })
192 .done(function (response) {
193 var data = JSON.parse(response);
194
195 for (var i = 0; i < data.length; ++i) {
196 if (!data[i].params) {
197 data[i].params = [];
198 }
199 }
200
201 if (typeof data.s == "undefined" || data.s != "error") {
202 onResultReadyCallback(data);
203 }
204 else {
205 onResultReadyCallback([]);
206 }
207
208 })
209 .fail(function(reason) {
210 onResultReadyCallback([]);
211 });
212 }
213 else {
214
215 if (!this._symbolSearch) {
216 throw "Datafeed error: inconsistent configuration (symbol search)";
217 }
218
219 var searchArgument = {
220 ticker: ticker,
221 exchange: exchange,
222 type: type,
223 onResultReadyCallback: onResultReadyCallback
224 };
225
226 if (this._initializationFinished) {
227 this._symbolSearch.searchSymbolsByName(searchArgument, MAX_SEARCH_RESULTS);
228 }
229 else {
230
231 var that = this;
232
233 this.on("initialized", function() {
234 that._symbolSearch.searchSymbolsByName(searchArgument, MAX_SEARCH_RESULTS);
235 });
236 }
237 }
238};
239
240
241Datafeeds.UDFCompatibleDatafeed.prototype._symbolResolveURL = "/symbols";
242
243
244// BEWARE: this function does not consider symbol's exchange
245Datafeeds.UDFCompatibleDatafeed.prototype.resolveSymbol = function(symbolName, onSymbolResolvedCallback, onResolveErrorCallback) {
246
247 var that = this;
248
249 if (!this._initializationFinished) {
250 this.on("initialized", function() {
251 that.resolveSymbol(symbolName, onSymbolResolvedCallback, onResolveErrorCallback);
252 });
253
254 return;
255 }
256
257 function onResultReady(data) {
258 var postProcessedData = data;
259 if (that.postProcessSymbolInfo) {
260 postProcessedData = that.postProcessSymbolInfo(postProcessedData);
261 }
262 onSymbolResolvedCallback(postProcessedData);
263 }
264
265 if (!this._configuration.supports_group_request) {
266 this._send(this._datafeedURL + this._symbolResolveURL, {
267 symbol: symbolName ? symbolName.toUpperCase() : ""
268 })
269 .done(function (response) {
270 var data = JSON.parse(response);
271
272 if (data.s && data.s != "ok") {
273 onResolveErrorCallback("unknown_symbol");
274 }
275 else {
276 onResultReady(data);
277 }
278 })
279 .fail(function(reason) {
280 onResolveErrorCallback("unknown_symbol");
281 });
282 }
283 else {
284 if (this._initializationFinished) {
285 this._symbolsStorage.resolveSymbol(symbolName, onResultReady, onResolveErrorCallback);
286 }
287 else {
288 this.on("initialized", function() {
289 that._symbolsStorage.resolveSymbol(symbolName, onResultReady, onResolveErrorCallback);
290 });
291 }
292 }
293};
294
295
296Datafeeds.UDFCompatibleDatafeed.prototype._historyURL = "/history";
297
298Datafeeds.UDFCompatibleDatafeed.prototype.getBars = function(symbolInfo, resolution, rangeStartDate, rangeEndDate, onDataCallback, onErrorCallback) {
299
300 // timestamp sample: 1399939200
301 if (rangeStartDate > 0 && (rangeStartDate + "").length > 10) {
302 throw "Got a JS time instead of Unix one.";
303 }
304
305 this._send(this._datafeedURL + this._historyURL, {
306 symbol: symbolInfo.ticker.toUpperCase(),
307 resolution: resolution,
308 from: rangeStartDate,
309 to: rangeEndDate
310 })
311 .done(function (response) {
312
313 var data = JSON.parse(response);
314
315 if (data.s != "ok") {
316 if (!!onErrorCallback) {
317 onErrorCallback(data.s);
318 }
319 return;
320 }
321
322 var bars = [];
323
324 // data is JSON having format {s: "status", v: [volumes], t: [times], o: [opens], h: [highs], l: [lows], c:[closes]}
325 var barsCount = data.t.length;
326
327 var volumePresent = typeof data.v != "undefined";
328 var ohlPresent = typeof data.o != "undefined";
329
330 for (var i = 0; i < barsCount; ++i) {
331
332 var barValue = {
333 time: data.t[i] * 1000,
334 close: data.c[i]
335 };
336
337 if (ohlPresent) {
338 barValue.open = data.o[i];
339 barValue.high = data.h[i];
340 barValue.low = data.l[i];
341 }
342 else {
343 barValue.open = barValue.high = barValue.low = barValue.close;
344 }
345
346 if (volumePresent) {
347 barValue.volume = data.v[i];
348 }
349
350 bars.push(barValue);
351 }
352
353
354 if (bars.length === 0) {
355 onErrorCallback("no data");
356 }
357
358 onDataCallback(bars);
359 })
360 .fail(function (arg) {
361 console.warn("getBars(): HTTP error " + JSON.stringify(arg));
362
363 if (!!onErrorCallback) {
364 onErrorCallback("network error: " + JSON.stringify(arg));
365 }
366 });
367};
368
369
370Datafeeds.UDFCompatibleDatafeed.prototype.subscribeBars = function(symbolInfo, resolution, onRealtimeCallback, listenerGUID) {
371 this._barsPulseUpdater.subscribeDataListener(symbolInfo, resolution, onRealtimeCallback, listenerGUID);
372};
373
374Datafeeds.UDFCompatibleDatafeed.prototype.unsubscribeBars = function(listenerGUID) {
375 this._barsPulseUpdater.unsubscribeDataListener(listenerGUID);
376};
377
378Datafeeds.UDFCompatibleDatafeed.prototype.calculateHistoryDepth = function(period, resolutionBack, intervalBack) {
379};
380
381Datafeeds.UDFCompatibleDatafeed.prototype.getQuotes = function(symbols, onDataCallback, onErrorCallback) {
382 this._send(this._datafeedURL + "/quotes", { symbols: symbols })
383 .done(function (response) {
384 var data = JSON.parse(response);
385 if (data.s == "ok") {
386 // JSON format is {s: "status", [{s: "symbol_status", n: "symbol_name", v: {"field1": "value1", "field2": "value2", ..., "fieldN": "valueN"}}]}
387 onDataCallback && onDataCallback(data.d);
388 } else {
389 onErrorCallback && onErrorCallback(data.errmsg);
390 }
391 })
392 .fail(function (arg) {
393 onErrorCallback && onErrorCallback("network error: " + arg);
394 });
395};
396
397Datafeeds.UDFCompatibleDatafeed.prototype.subscribeQuotes = function(symbols, fastSymbols, onRealtimeCallback, listenerGUID) {
398 this._quotesPulseUpdater.subscribeDataListener(symbols, fastSymbols, onRealtimeCallback, listenerGUID);
399};
400
401Datafeeds.UDFCompatibleDatafeed.prototype.unsubscribeQuotes = function(listenerGUID) {
402 this._quotesPulseUpdater.unsubscribeDataListener(listenerGUID);
403};
404
405// ==================================================================================================================================================
406// ==================================================================================================================================================
407// ==================================================================================================================================================
408
409/*
410 It's a symbol storage component for ExternalDatafeed. This component can
411 * interact to UDF-compatible datafeed which supports whole group info requesting
412 * do symbol resolving -- return symbol info by its name
413*/
414Datafeeds.SymbolsStorage = function(datafeed) {
415 this._datafeed = datafeed;
416
417 this._exchangesList = ["NYSE", "FOREX", "AMEX"];
418 this._exchangesWaitingForData = {};
419 this._exchangesDataCache = {};
420
421 this._symbolsInfo = {};
422 this._symbolsList = [];
423
424 this._requestFullSymbolsList();
425};
426
427
428
429Datafeeds.SymbolsStorage.prototype._requestFullSymbolsList = function() {
430
431 var that = this;
432 var datafeed = this._datafeed;
433
434 for (var i = 0; i < this._exchangesList.length; ++i) {
435
436 var exchange = this._exchangesList[i];
437
438 if (this._exchangesDataCache.hasOwnProperty(exchange)) {
439 continue;
440 }
441
442 this._exchangesDataCache[exchange] = true;
443
444 this._exchangesWaitingForData[exchange] = "waiting_for_data";
445
446 this._datafeed._send(this._datafeed._datafeedURL + "/symbol_info", {
447 group: exchange
448 })
449 .done(function(exchange) {
450 return function(response) {
451 that._onExchangeDataReceived(exchange, JSON.parse(response));
452 that._onAnyExchangeResponseReceived(exchange);
453 }
454 }(exchange))
455 .fail(function(exchange) {
456 return function (reason) {
457 that._onAnyExchangeResponseReceived(exchange);
458 };
459 }(exchange));
460 }
461}
462
463
464
465Datafeeds.SymbolsStorage.prototype._onExchangeDataReceived = function(exchangeName, data) {
466
467 function tableField(data, name, index) {
468 return data[name] instanceof Array
469 ? data[name][index]
470 : data[name];
471 }
472
473 try
474 {
475 for (var symbolIndex = 0; symbolIndex < data.symbol.length; ++symbolIndex) {
476
477 var symbolName = data.symbol[symbolIndex];
478 var listedExchange = tableField(data, "exchange-listed", symbolIndex);
479 var tradedExchange = tableField(data, "exchange-traded", symbolIndex);
480 var fullName = tradedExchange + ":" + symbolName;
481
482 // This feature support is not implemented yet
483 // var hasDWM = tableField(data, "has-dwm", symbolIndex);
484
485 var hasIntraday = tableField(data, "has-intraday", symbolIndex);
486
487 var tickerPresent = typeof data["ticker"] != "undefined";
488
489 var symbolInfo = {
490 name: symbolName,
491 base_name: [listedExchange + ":" + symbolName],
492 description: tableField(data, "description", symbolIndex),
493 full_name: fullName,
494 legs: [fullName],
495 has_intraday: hasIntraday,
496 has_no_volume: tableField(data, "has-no-volume", symbolIndex),
497 listed_exchange: listedExchange,
498 exchange: tradedExchange,
499 minmov: tableField(data, "minmovement", symbolIndex) || tableField(data, "minmov", symbolIndex) ,
500 pointvalue: tableField(data, "pointvalue", symbolIndex),
501 pricescale: tableField(data, "pricescale", symbolIndex),
502 type: tableField(data, "type", symbolIndex),
503 session: tableField(data, "session-regular", symbolIndex),
504 ticker: tickerPresent ? tableField(data, "ticker", symbolIndex) : symbolName,
505 timezone: tableField(data, "timezone", symbolIndex),
506 supported_resolutions: tableField(data, "supported-resolutions", symbolIndex) || this._datafeed.defaultConfiguration().supported_resolutions,
507 force_session_rebuild: tableField(data, "force-session-rebuild", symbolIndex) || false,
508 has_daily: tableField(data, "has-daily", symbolIndex) || true,
509 intraday_multipliers: tableField(data, "intraday-multipliers", symbolIndex) || ["1", "5", "15", "30", "60"],
510 has_fractional_volume: tableField(data, "has-fractional-volume", symbolIndex) || false,
511 has_weekly_and_monthly: tableField(data, "has-weekly-and-monthly", symbolIndex) || false,
512 has_empty_bars: tableField(data, "has-empty-bars", symbolIndex) || false,
513 volume_precision: tableField(data, "volume-precision", symbolIndex) || 0
514 };
515
516 this._symbolsInfo[symbolInfo.ticker] = this._symbolsInfo[symbolName] = this._symbolsInfo[fullName] = symbolInfo;
517 this._symbolsList.push(symbolName);
518 }
519 }
520 catch (error) {
521 throw "API error when processing exchange `" + exchangeName + "` symbol #" + symbolIndex + ": " + error;
522 }
523};
524
525
526Datafeeds.SymbolsStorage.prototype._onAnyExchangeResponseReceived = function(exchangeName) {
527
528 delete this._exchangesWaitingForData[exchangeName];
529
530 var allDataReady = Object.keys(this._exchangesWaitingForData).length === 0;
531
532 if (allDataReady) {
533 this._symbolsList.sort();
534 this._datafeed._logMessage("All exchanges data ready");
535 this._datafeed.onInitialized();
536 }
537};
538
539
540// BEWARE: this function does not consider symbol's exchange
541Datafeeds.SymbolsStorage.prototype.resolveSymbol = function(symbolName, onSymbolResolvedCallback, onResolveErrorCallback) {
542
543 if (!this._symbolsInfo.hasOwnProperty(symbolName)) {
544 onResolveErrorCallback("invalid symbol");
545 }
546 else {
547 onSymbolResolvedCallback(this._symbolsInfo[symbolName]);
548 }
549
550};
551
552
553// ==================================================================================================================================================
554// ==================================================================================================================================================
555// ==================================================================================================================================================
556
557/*
558 It's a symbol search component for ExternalDatafeed. This component can do symbol search only.
559 This component strongly depends on SymbolsDataStorage and cannot work without it. Maybe, it would be
560 better to merge it to SymbolsDataStorage.
561*/
562
563Datafeeds.SymbolSearchComponent = function(datafeed) {
564 this._datafeed = datafeed;
565};
566
567
568
569// searchArgument = { ticker, onResultReadyCallback}
570Datafeeds.SymbolSearchComponent.prototype.searchSymbolsByName = function(searchArgument, maxSearchResults) {
571
572 if (!this._datafeed._symbolsStorage) {
573 throw "Cannot use local symbol search when no groups information is available";
574 }
575
576 var symbolsStorage = this._datafeed._symbolsStorage;
577
578 var results = [];
579 var queryIsEmpty = !searchArgument.ticker || searchArgument.ticker.length === 0;
580
581 for (var i = 0; i < symbolsStorage._symbolsList.length; ++i) {
582 var symbolName = symbolsStorage._symbolsList[i];
583 var item = symbolsStorage._symbolsInfo[symbolName];
584
585 if (searchArgument.type && searchArgument.type.length > 0 && item.type != searchArgument.type) {
586 continue;
587 }
588 if (searchArgument.exchange && searchArgument.exchange.length > 0 && item.exchange != searchArgument.exchange) {
589 continue;
590 }
591 if (queryIsEmpty || item.name.indexOf(searchArgument.ticker) === 0) {
592 results.push({
593 symbol: item.name,
594 full_name: item.full_name,
595 description: item.description,
596 exchange: item.exchange,
597 params: [],
598 type: item.type,
599 ticker: item.name
600 });
601 }
602
603 if (results.length >= maxSearchResults) {
604 break;
605 }
606 }
607
608 searchArgument.onResultReadyCallback(results);
609}
610
611
612
613// ==================================================================================================================================================
614// ==================================================================================================================================================
615// ==================================================================================================================================================
616
617/*
618 This is a pulse updating components for ExternalDatafeed. They emulates realtime updates with periodic requests.
619*/
620
621Datafeeds.DataPulseUpdater = function(datafeed, updateFrequency) {
622 this._datafeed = datafeed;
623 this._subscribers = {};
624
625 this._requestsPending = 0;
626 var that = this;
627
628 var update = function() {
629 if (that._requestsPending > 0) {
630 return;
631 }
632
633 for (var listenerGUID in that._subscribers) {
634 var subscriptionRecord = that._subscribers[listenerGUID];
635 var resolution = subscriptionRecord.resolution;
636 var datesRangeRight = Math.round(Date.now() / 1000);
637
638 // BEWARE: please note we really need 2 bars, not the only last one
639 // see the explanation below. `10` is the `large enough` value to work around holidays
640 var datesRangeLeft = datesRangeRight - that.periodLengthSeconds(resolution, 10);
641
642 that._requestsPending++;
643
644 (function(_subscriptionRecord) {
645 that._datafeed.getBars(_subscriptionRecord.symbolInfo, resolution, datesRangeLeft, datesRangeRight, function(bars) {
646 that._requestsPending--;
647
648 // means the subscription was cancelled while waiting for data
649 if (!that._subscribers.hasOwnProperty(listenerGUID)) {
650 return;
651 }
652
653 if (bars.length === 0) {
654 return;
655 }
656 var lastBar = bars[bars.length - 1];
657 if (!isNaN(_subscriptionRecord.lastBarTime) && lastBar.time < _subscriptionRecord.lastBarTime) {
658 return;
659 }
660
661 var subscribers = _subscriptionRecord.listeners;
662
663 // BEWARE: this one isn't working when first update comes and this update makes a new bar. In this case
664 // _subscriptionRecord.lastBarTime = NaN
665 var isNewBar = !isNaN(_subscriptionRecord.lastBarTime) && lastBar.time > _subscriptionRecord.lastBarTime;
666
667 // 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
668 // old bar's last 5 seconds trades will be lost). Thus, at fist we should broadcast old bar updates when it's ready.
669 if (isNewBar) {
670
671 if (bars.length < 2) {
672 throw "Not enough bars in history for proper pulse update. Need at least 2.";
673 }
674
675 var previousBar = bars[bars.length - 2];
676 for (var i =0; i < subscribers.length; ++i) {
677 subscribers[i](previousBar);
678 }
679 }
680
681 _subscriptionRecord.lastBarTime = lastBar.time;
682
683 for (var i =0; i < subscribers.length; ++i) {
684 subscribers[i](lastBar);
685 }
686 },
687
688 // on error
689 function() {
690 that._requestsPending--;
691 });
692 })(subscriptionRecord);
693
694 }
695 }
696
697 if (typeof updateFrequency != "undefined" && updateFrequency > 0) {
698 setInterval(update, updateFrequency);
699 }
700}
701
702
703Datafeeds.DataPulseUpdater.prototype.unsubscribeDataListener = function(listenerGUID) {
704 this._datafeed._logMessage("Unsubscribing " + listenerGUID);
705 delete this._subscribers[listenerGUID];
706}
707
708
709Datafeeds.DataPulseUpdater.prototype.subscribeDataListener = function(symbolInfo, resolution, newDataCallback, listenerGUID) {
710
711 this._datafeed._logMessage("Subscribing " + listenerGUID);
712
713 var key = symbolInfo.name + ", " + resolution;
714
715 if (!this._subscribers.hasOwnProperty(listenerGUID)) {
716
717 this._subscribers[listenerGUID] = {
718 symbolInfo: symbolInfo,
719 resolution: resolution,
720 lastBarTime: NaN,
721 listeners: []
722 };
723 }
724
725 this._subscribers[listenerGUID].listeners.push(newDataCallback);
726}
727
728
729Datafeeds.DataPulseUpdater.prototype.periodLengthSeconds = function(resolution, requiredPeriodsCount) {
730 var daysCount = 0;
731
732 if (resolution == "D") {
733 daysCount = requiredPeriodsCount;
734 }
735 else if (resolution == "M") {
736 daysCount = 31 * requiredPeriodsCount;
737 }
738 else if (resolution == "W") {
739 daysCount = 7 * requiredPeriodsCount;
740 }
741 else {
742 daysCount = requiredPeriodsCount * resolution / (24 * 60);
743 }
744
745 return daysCount * 24 * 60 * 60;
746}
747
748
749Datafeeds.QuotesPulseUpdater = function(datafeed) {
750 this._datafeed = datafeed;
751 this._subscribers = {};
752 this._updateInterval = 60 * 1000;
753 this._fastUpdateInterval = 10 * 1000;
754 this._requestsPending = 0;
755
756 var that = this;
757
758 setInterval(function() {
759 that._updateQuotes(function(subscriptionRecord) { return subscriptionRecord.symbols; })
760 }, this._updateInterval);
761
762 setInterval(function() {
763 that._updateQuotes(function(subscriptionRecord) { return subscriptionRecord.fastSymbols.length > 0 ? subscriptionRecord.fastSymbols : subscriptionRecord.symbols; })
764 }, this._fastUpdateInterval);
765};
766
767Datafeeds.QuotesPulseUpdater.prototype.subscribeDataListener = function(symbols, fastSymbols, newDataCallback, listenerGUID) {
768 if (!this._subscribers.hasOwnProperty(listenerGUID)) {
769 this._subscribers[listenerGUID] = {
770 symbols: symbols,
771 fastSymbols: fastSymbols,
772 listeners: []
773 };
774 }
775 this._subscribers[listenerGUID].listeners.push(newDataCallback);
776};
777
778Datafeeds.QuotesPulseUpdater.prototype.unsubscribeDataListener = function(listenerGUID) {
779 delete this._subscribers[listenerGUID];
780};
781
782Datafeeds.QuotesPulseUpdater.prototype._updateQuotes = function(symbolsGetter) {
783 if (this._requestsPending > 0) {
784 return;
785 }
786
787 var that = this;
788 for (var listenerGUID in this._subscribers) {
789 this._requestsPending++;
790
791 var subscriptionRecord = this._subscribers[listenerGUID];
792 this._datafeed.getQuotes(symbolsGetter(subscriptionRecord),
793 // onDataCallback
794 function(subscribers, guid) {
795 return function(data) {
796 that._requestsPending--;
797
798 // means the subscription was cancelled while waiting for data
799 if (!that._subscribers.hasOwnProperty(guid)) {
800 return;
801 }
802
803 for (var i =0; i < subscribers.length; ++i) {
804 subscribers[i](data);
805 }
806 }
807 }(subscriptionRecord.listeners, listenerGUID),
808 // onErrorCallback
809 function (error) {
810 that._requestsPending--;
811 });
812 }
813};