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