binbot-charts 0.0.21 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/TVChartContainer.js +41 -77
- package/dist/components/datafeed.js +131 -100
- package/dist/components/streaming.js +1 -1
- package/package.json +5 -4
- package/dist/datafeeds/README.md +0 -3
- package/dist/datafeeds/udf/README.md +0 -46
- package/dist/datafeeds/udf/dist/bundle.js +0 -1
- package/dist/datafeeds/udf/lib/data-pulse-provider.js +0 -104
- package/dist/datafeeds/udf/lib/helpers.js +0 -20
- package/dist/datafeeds/udf/lib/history-provider.js +0 -73
- package/dist/datafeeds/udf/lib/iquotes-provider.js +0 -1
- package/dist/datafeeds/udf/lib/quotes-provider.js +0 -25
- package/dist/datafeeds/udf/lib/quotes-pulse-provider.js +0 -44
- package/dist/datafeeds/udf/lib/requester.js +0 -28
- package/dist/datafeeds/udf/lib/symbols-storage.js +0 -180
- package/dist/datafeeds/udf/lib/udf-compatible-datafeed-base.js +0 -252
- package/dist/datafeeds/udf/lib/udf-compatible-datafeed.js +0 -10
- package/dist/datafeeds/udf/package.json +0 -17
- package/dist/datafeeds/udf/rollup.config.js +0 -25
- package/dist/datafeeds/udf/src/data-pulse-provider.ts +0 -152
- package/dist/datafeeds/udf/src/helpers.ts +0 -38
- package/dist/datafeeds/udf/src/history-provider.ts +0 -134
- package/dist/datafeeds/udf/src/iquotes-provider.ts +0 -14
- package/dist/datafeeds/udf/src/quotes-provider.ts +0 -37
- package/dist/datafeeds/udf/src/quotes-pulse-provider.ts +0 -85
- package/dist/datafeeds/udf/src/requester.ts +0 -39
- package/dist/datafeeds/udf/src/symbols-storage.ts +0 -298
- package/dist/datafeeds/udf/src/udf-compatible-datafeed-base.ts +0 -369
- package/dist/datafeeds/udf/src/udf-compatible-datafeed.ts +0 -11
- package/dist/datafeeds/udf/tsconfig.json +0 -25
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { getErrorMessage, } from './helpers';
|
|
2
|
-
export class HistoryProvider {
|
|
3
|
-
constructor(datafeedUrl, requester) {
|
|
4
|
-
this._datafeedUrl = datafeedUrl;
|
|
5
|
-
this._requester = requester;
|
|
6
|
-
}
|
|
7
|
-
getBars(symbolInfo, resolution, periodParams) {
|
|
8
|
-
const requestParams = {
|
|
9
|
-
symbol: symbolInfo.ticker || '',
|
|
10
|
-
resolution: resolution,
|
|
11
|
-
from: periodParams.from,
|
|
12
|
-
to: periodParams.to,
|
|
13
|
-
};
|
|
14
|
-
if (periodParams.countBack !== undefined) {
|
|
15
|
-
requestParams.countback = periodParams.countBack;
|
|
16
|
-
}
|
|
17
|
-
if (symbolInfo.currency_code !== undefined) {
|
|
18
|
-
requestParams.currencyCode = symbolInfo.currency_code;
|
|
19
|
-
}
|
|
20
|
-
if (symbolInfo.unit_id !== undefined) {
|
|
21
|
-
requestParams.unitId = symbolInfo.unit_id;
|
|
22
|
-
}
|
|
23
|
-
return new Promise((resolve, reject) => {
|
|
24
|
-
this._requester.sendRequest(this._datafeedUrl, 'history', requestParams)
|
|
25
|
-
.then((response) => {
|
|
26
|
-
if (response.s !== 'ok' && response.s !== 'no_data') {
|
|
27
|
-
reject(response.errmsg);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const bars = [];
|
|
31
|
-
const meta = {
|
|
32
|
-
noData: false,
|
|
33
|
-
};
|
|
34
|
-
if (response.s === 'no_data') {
|
|
35
|
-
meta.noData = true;
|
|
36
|
-
meta.nextTime = response.nextTime;
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
const volumePresent = response.v !== undefined;
|
|
40
|
-
const ohlPresent = response.o !== undefined;
|
|
41
|
-
for (let i = 0; i < response.t.length; ++i) {
|
|
42
|
-
const barValue = {
|
|
43
|
-
time: response.t[i] * 1000,
|
|
44
|
-
close: parseFloat(response.c[i]),
|
|
45
|
-
open: parseFloat(response.c[i]),
|
|
46
|
-
high: parseFloat(response.c[i]),
|
|
47
|
-
low: parseFloat(response.c[i]),
|
|
48
|
-
};
|
|
49
|
-
if (ohlPresent) {
|
|
50
|
-
barValue.open = parseFloat(response.o[i]);
|
|
51
|
-
barValue.high = parseFloat(response.h[i]);
|
|
52
|
-
barValue.low = parseFloat(response.l[i]);
|
|
53
|
-
}
|
|
54
|
-
if (volumePresent) {
|
|
55
|
-
barValue.volume = parseFloat(response.v[i]);
|
|
56
|
-
}
|
|
57
|
-
bars.push(barValue);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
resolve({
|
|
61
|
-
bars: bars,
|
|
62
|
-
meta: meta,
|
|
63
|
-
});
|
|
64
|
-
})
|
|
65
|
-
.catch((reason) => {
|
|
66
|
-
const reasonString = getErrorMessage(reason);
|
|
67
|
-
// tslint:disable-next-line:no-console
|
|
68
|
-
console.warn(`HistoryProvider: getBars() failed, error=${reasonString}`);
|
|
69
|
-
reject(reasonString);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { getErrorMessage, logMessage, } from './helpers';
|
|
2
|
-
export class QuotesProvider {
|
|
3
|
-
constructor(datafeedUrl, requester) {
|
|
4
|
-
this._datafeedUrl = datafeedUrl;
|
|
5
|
-
this._requester = requester;
|
|
6
|
-
}
|
|
7
|
-
getQuotes(symbols) {
|
|
8
|
-
return new Promise((resolve, reject) => {
|
|
9
|
-
this._requester.sendRequest(this._datafeedUrl, 'quotes', { symbols: symbols })
|
|
10
|
-
.then((response) => {
|
|
11
|
-
if (response.s === 'ok') {
|
|
12
|
-
resolve(response.d);
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
reject(response.errmsg);
|
|
16
|
-
}
|
|
17
|
-
})
|
|
18
|
-
.catch((error) => {
|
|
19
|
-
const errorMessage = getErrorMessage(error);
|
|
20
|
-
logMessage(`QuotesProvider: getQuotes failed, error=${errorMessage}`);
|
|
21
|
-
reject(`network error: ${errorMessage}`);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { getErrorMessage, logMessage, } from './helpers';
|
|
2
|
-
export class QuotesPulseProvider {
|
|
3
|
-
constructor(quotesProvider) {
|
|
4
|
-
this._subscribers = {};
|
|
5
|
-
this._requestsPending = 0;
|
|
6
|
-
this._quotesProvider = quotesProvider;
|
|
7
|
-
setInterval(this._updateQuotes.bind(this, 1 /* Fast */), 10000 /* Fast */);
|
|
8
|
-
setInterval(this._updateQuotes.bind(this, 0 /* General */), 60000 /* General */);
|
|
9
|
-
}
|
|
10
|
-
subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
|
|
11
|
-
this._subscribers[listenerGuid] = {
|
|
12
|
-
symbols: symbols,
|
|
13
|
-
fastSymbols: fastSymbols,
|
|
14
|
-
listener: onRealtimeCallback,
|
|
15
|
-
};
|
|
16
|
-
logMessage(`QuotesPulseProvider: subscribed quotes with #${listenerGuid}`);
|
|
17
|
-
}
|
|
18
|
-
unsubscribeQuotes(listenerGuid) {
|
|
19
|
-
delete this._subscribers[listenerGuid];
|
|
20
|
-
logMessage(`QuotesPulseProvider: unsubscribed quotes with #${listenerGuid}`);
|
|
21
|
-
}
|
|
22
|
-
_updateQuotes(updateType) {
|
|
23
|
-
if (this._requestsPending > 0) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
for (const listenerGuid in this._subscribers) { // tslint:disable-line:forin
|
|
27
|
-
this._requestsPending++;
|
|
28
|
-
const subscriptionRecord = this._subscribers[listenerGuid];
|
|
29
|
-
this._quotesProvider.getQuotes(updateType === 1 /* Fast */ ? subscriptionRecord.fastSymbols : subscriptionRecord.symbols)
|
|
30
|
-
.then((data) => {
|
|
31
|
-
this._requestsPending--;
|
|
32
|
-
if (!this._subscribers.hasOwnProperty(listenerGuid)) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
subscriptionRecord.listener(data);
|
|
36
|
-
logMessage(`QuotesPulseProvider: data for #${listenerGuid} (${updateType}) updated successfully, pending=${this._requestsPending}`);
|
|
37
|
-
})
|
|
38
|
-
.catch((reason) => {
|
|
39
|
-
this._requestsPending--;
|
|
40
|
-
logMessage(`QuotesPulseProvider: data for #${listenerGuid} (${updateType}) updated with error=${getErrorMessage(reason)}, pending=${this._requestsPending}`);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { logMessage } from './helpers';
|
|
2
|
-
export class Requester {
|
|
3
|
-
constructor(headers) {
|
|
4
|
-
if (headers) {
|
|
5
|
-
this._headers = headers;
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
sendRequest(datafeedUrl, urlPath, params) {
|
|
9
|
-
if (params !== undefined) {
|
|
10
|
-
const paramKeys = Object.keys(params);
|
|
11
|
-
if (paramKeys.length !== 0) {
|
|
12
|
-
urlPath += '?';
|
|
13
|
-
}
|
|
14
|
-
urlPath += paramKeys.map((key) => {
|
|
15
|
-
return `${encodeURIComponent(key)}=${encodeURIComponent(params[key].toString())}`;
|
|
16
|
-
}).join('&');
|
|
17
|
-
}
|
|
18
|
-
logMessage('New request: ' + urlPath);
|
|
19
|
-
// Send user cookies if the URL is on the same origin as the calling script.
|
|
20
|
-
const options = { credentials: 'same-origin' };
|
|
21
|
-
if (this._headers !== undefined) {
|
|
22
|
-
options.headers = this._headers;
|
|
23
|
-
}
|
|
24
|
-
return fetch(`${datafeedUrl}/${urlPath}`, options)
|
|
25
|
-
.then((response) => response.text())
|
|
26
|
-
.then((responseTest) => JSON.parse(responseTest));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { getErrorMessage, logMessage, } from './helpers';
|
|
2
|
-
function extractField(data, field, arrayIndex, valueIsArray) {
|
|
3
|
-
const value = data[field];
|
|
4
|
-
if (Array.isArray(value) && (!valueIsArray || Array.isArray(value[0]))) {
|
|
5
|
-
return value[arrayIndex];
|
|
6
|
-
}
|
|
7
|
-
return value;
|
|
8
|
-
}
|
|
9
|
-
function symbolKey(symbol, currency, unit) {
|
|
10
|
-
// here we're using a separator that quite possible shouldn't be in a real symbol name
|
|
11
|
-
return symbol + (currency !== undefined ? '_%|#|%_' + currency : '') + (unit !== undefined ? '_%|#|%_' + unit : '');
|
|
12
|
-
}
|
|
13
|
-
export class SymbolsStorage {
|
|
14
|
-
constructor(datafeedUrl, datafeedSupportedResolutions, requester) {
|
|
15
|
-
this._exchangesList = ['NYSE', 'FOREX', 'AMEX'];
|
|
16
|
-
this._symbolsInfo = {};
|
|
17
|
-
this._symbolsList = [];
|
|
18
|
-
this._datafeedUrl = datafeedUrl;
|
|
19
|
-
this._datafeedSupportedResolutions = datafeedSupportedResolutions;
|
|
20
|
-
this._requester = requester;
|
|
21
|
-
this._readyPromise = this._init();
|
|
22
|
-
this._readyPromise.catch((error) => {
|
|
23
|
-
// seems it is impossible
|
|
24
|
-
// tslint:disable-next-line:no-console
|
|
25
|
-
console.error(`SymbolsStorage: Cannot init, error=${error.toString()}`);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
// BEWARE: this function does not consider symbol's exchange
|
|
29
|
-
resolveSymbol(symbolName, currencyCode, unitId) {
|
|
30
|
-
return this._readyPromise.then(() => {
|
|
31
|
-
const symbolInfo = this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)];
|
|
32
|
-
if (symbolInfo === undefined) {
|
|
33
|
-
return Promise.reject('invalid symbol');
|
|
34
|
-
}
|
|
35
|
-
return Promise.resolve(symbolInfo);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
searchSymbols(searchString, exchange, symbolType, maxSearchResults) {
|
|
39
|
-
return this._readyPromise.then(() => {
|
|
40
|
-
const weightedResult = [];
|
|
41
|
-
const queryIsEmpty = searchString.length === 0;
|
|
42
|
-
searchString = searchString.toUpperCase();
|
|
43
|
-
for (const symbolName of this._symbolsList) {
|
|
44
|
-
const symbolInfo = this._symbolsInfo[symbolName];
|
|
45
|
-
if (symbolInfo === undefined) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (symbolType.length > 0 && symbolInfo.type !== symbolType) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (exchange && exchange.length > 0 && symbolInfo.exchange !== exchange) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const positionInName = symbolInfo.name.toUpperCase().indexOf(searchString);
|
|
55
|
-
const positionInDescription = symbolInfo.description.toUpperCase().indexOf(searchString);
|
|
56
|
-
if (queryIsEmpty || positionInName >= 0 || positionInDescription >= 0) {
|
|
57
|
-
const alreadyExists = weightedResult.some((item) => item.symbolInfo === symbolInfo);
|
|
58
|
-
if (!alreadyExists) {
|
|
59
|
-
const weight = positionInName >= 0 ? positionInName : 8000 + positionInDescription;
|
|
60
|
-
weightedResult.push({ symbolInfo: symbolInfo, weight: weight });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const result = weightedResult
|
|
65
|
-
.sort((item1, item2) => item1.weight - item2.weight)
|
|
66
|
-
.slice(0, maxSearchResults)
|
|
67
|
-
.map((item) => {
|
|
68
|
-
const symbolInfo = item.symbolInfo;
|
|
69
|
-
return {
|
|
70
|
-
symbol: symbolInfo.name,
|
|
71
|
-
full_name: symbolInfo.full_name,
|
|
72
|
-
description: symbolInfo.description,
|
|
73
|
-
exchange: symbolInfo.exchange,
|
|
74
|
-
params: [],
|
|
75
|
-
type: symbolInfo.type,
|
|
76
|
-
ticker: symbolInfo.name,
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
return Promise.resolve(result);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
_init() {
|
|
83
|
-
const promises = [];
|
|
84
|
-
const alreadyRequestedExchanges = {};
|
|
85
|
-
for (const exchange of this._exchangesList) {
|
|
86
|
-
if (alreadyRequestedExchanges[exchange]) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
alreadyRequestedExchanges[exchange] = true;
|
|
90
|
-
promises.push(this._requestExchangeData(exchange));
|
|
91
|
-
}
|
|
92
|
-
return Promise.all(promises)
|
|
93
|
-
.then(() => {
|
|
94
|
-
this._symbolsList.sort();
|
|
95
|
-
logMessage('SymbolsStorage: All exchanges data loaded');
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
_requestExchangeData(exchange) {
|
|
99
|
-
return new Promise((resolve, reject) => {
|
|
100
|
-
this._requester.sendRequest(this._datafeedUrl, 'symbol_info', { group: exchange })
|
|
101
|
-
.then((response) => {
|
|
102
|
-
try {
|
|
103
|
-
this._onExchangeDataReceived(exchange, response);
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
reject(error);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
resolve();
|
|
110
|
-
})
|
|
111
|
-
.catch((reason) => {
|
|
112
|
-
logMessage(`SymbolsStorage: Request data for exchange '${exchange}' failed, reason=${getErrorMessage(reason)}`);
|
|
113
|
-
resolve();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
_onExchangeDataReceived(exchange, data) {
|
|
118
|
-
let symbolIndex = 0;
|
|
119
|
-
try {
|
|
120
|
-
const symbolsCount = data.symbol.length;
|
|
121
|
-
const tickerPresent = data.ticker !== undefined;
|
|
122
|
-
for (; symbolIndex < symbolsCount; ++symbolIndex) {
|
|
123
|
-
const symbolName = data.symbol[symbolIndex];
|
|
124
|
-
const listedExchange = extractField(data, 'exchange-listed', symbolIndex);
|
|
125
|
-
const tradedExchange = extractField(data, 'exchange-traded', symbolIndex);
|
|
126
|
-
const fullName = tradedExchange + ':' + symbolName;
|
|
127
|
-
const currencyCode = extractField(data, 'currency-code', symbolIndex);
|
|
128
|
-
const unitId = extractField(data, 'unit-id', symbolIndex);
|
|
129
|
-
const ticker = tickerPresent ? extractField(data, 'ticker', symbolIndex) : symbolName;
|
|
130
|
-
const symbolInfo = {
|
|
131
|
-
ticker: ticker,
|
|
132
|
-
name: symbolName,
|
|
133
|
-
base_name: [listedExchange + ':' + symbolName],
|
|
134
|
-
full_name: fullName,
|
|
135
|
-
listed_exchange: listedExchange,
|
|
136
|
-
exchange: tradedExchange,
|
|
137
|
-
currency_code: currencyCode,
|
|
138
|
-
original_currency_code: extractField(data, 'original-currency-code', symbolIndex),
|
|
139
|
-
unit_id: unitId,
|
|
140
|
-
original_unit_id: extractField(data, 'original-unit-id', symbolIndex),
|
|
141
|
-
unit_conversion_types: extractField(data, 'unit-conversion-types', symbolIndex, true),
|
|
142
|
-
description: extractField(data, 'description', symbolIndex),
|
|
143
|
-
has_intraday: definedValueOrDefault(extractField(data, 'has-intraday', symbolIndex), false),
|
|
144
|
-
has_no_volume: definedValueOrDefault(extractField(data, 'has-no-volume', symbolIndex), false),
|
|
145
|
-
minmov: extractField(data, 'minmovement', symbolIndex) || extractField(data, 'minmov', symbolIndex) || 0,
|
|
146
|
-
minmove2: extractField(data, 'minmove2', symbolIndex) || extractField(data, 'minmov2', symbolIndex),
|
|
147
|
-
fractional: extractField(data, 'fractional', symbolIndex),
|
|
148
|
-
pricescale: extractField(data, 'pricescale', symbolIndex),
|
|
149
|
-
type: extractField(data, 'type', symbolIndex),
|
|
150
|
-
session: extractField(data, 'session-regular', symbolIndex),
|
|
151
|
-
session_holidays: extractField(data, 'session-holidays', symbolIndex),
|
|
152
|
-
corrections: extractField(data, 'corrections', symbolIndex),
|
|
153
|
-
timezone: extractField(data, 'timezone', symbolIndex),
|
|
154
|
-
supported_resolutions: definedValueOrDefault(extractField(data, 'supported-resolutions', symbolIndex, true), this._datafeedSupportedResolutions),
|
|
155
|
-
has_daily: definedValueOrDefault(extractField(data, 'has-daily', symbolIndex), true),
|
|
156
|
-
intraday_multipliers: definedValueOrDefault(extractField(data, 'intraday-multipliers', symbolIndex, true), ['1', '5', '15', '30', '60']),
|
|
157
|
-
has_weekly_and_monthly: extractField(data, 'has-weekly-and-monthly', symbolIndex),
|
|
158
|
-
has_empty_bars: extractField(data, 'has-empty-bars', symbolIndex),
|
|
159
|
-
volume_precision: definedValueOrDefault(extractField(data, 'volume-precision', symbolIndex), 0),
|
|
160
|
-
format: 'price',
|
|
161
|
-
};
|
|
162
|
-
this._symbolsInfo[ticker] = symbolInfo;
|
|
163
|
-
this._symbolsInfo[symbolName] = symbolInfo;
|
|
164
|
-
this._symbolsInfo[fullName] = symbolInfo;
|
|
165
|
-
if (currencyCode !== undefined || unitId !== undefined) {
|
|
166
|
-
this._symbolsInfo[symbolKey(ticker, currencyCode, unitId)] = symbolInfo;
|
|
167
|
-
this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)] = symbolInfo;
|
|
168
|
-
this._symbolsInfo[symbolKey(fullName, currencyCode, unitId)] = symbolInfo;
|
|
169
|
-
}
|
|
170
|
-
this._symbolsList.push(symbolName);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
throw new Error(`SymbolsStorage: API error when processing exchange ${exchange} symbol #${symbolIndex} (${data.symbol[symbolIndex]}): ${error.message}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function definedValueOrDefault(value, defaultValue) {
|
|
179
|
-
return value !== undefined ? value : defaultValue;
|
|
180
|
-
}
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { getErrorMessage, logMessage, } from './helpers';
|
|
2
|
-
import { HistoryProvider, } from './history-provider';
|
|
3
|
-
import { DataPulseProvider } from './data-pulse-provider';
|
|
4
|
-
import { QuotesPulseProvider } from './quotes-pulse-provider';
|
|
5
|
-
import { SymbolsStorage } from './symbols-storage';
|
|
6
|
-
function extractField(data, field, arrayIndex) {
|
|
7
|
-
const value = data[field];
|
|
8
|
-
return Array.isArray(value) ? value[arrayIndex] : value;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* This class implements interaction with UDF-compatible datafeed.
|
|
12
|
-
* See UDF protocol reference at https://github.com/tradingview/charting_library/wiki/UDF
|
|
13
|
-
*/
|
|
14
|
-
export class UDFCompatibleDatafeedBase {
|
|
15
|
-
constructor(datafeedURL, quotesProvider, requester, updateFrequency = 10 * 1000) {
|
|
16
|
-
this._configuration = defaultConfiguration();
|
|
17
|
-
this._symbolsStorage = null;
|
|
18
|
-
this._datafeedURL = datafeedURL;
|
|
19
|
-
this._requester = requester;
|
|
20
|
-
this._historyProvider = new HistoryProvider(datafeedURL, this._requester);
|
|
21
|
-
this._quotesProvider = quotesProvider;
|
|
22
|
-
this._dataPulseProvider = new DataPulseProvider(this._historyProvider, updateFrequency);
|
|
23
|
-
this._quotesPulseProvider = new QuotesPulseProvider(this._quotesProvider);
|
|
24
|
-
this._configurationReadyPromise = this._requestConfiguration()
|
|
25
|
-
.then((configuration) => {
|
|
26
|
-
if (configuration === null) {
|
|
27
|
-
configuration = defaultConfiguration();
|
|
28
|
-
}
|
|
29
|
-
this._setupWithConfiguration(configuration);
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
onReady(callback) {
|
|
33
|
-
this._configurationReadyPromise.then(() => {
|
|
34
|
-
callback(this._configuration);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
getQuotes(symbols, onDataCallback, onErrorCallback) {
|
|
38
|
-
this._quotesProvider.getQuotes(symbols).then(onDataCallback).catch(onErrorCallback);
|
|
39
|
-
}
|
|
40
|
-
subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid) {
|
|
41
|
-
this._quotesPulseProvider.subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid);
|
|
42
|
-
}
|
|
43
|
-
unsubscribeQuotes(listenerGuid) {
|
|
44
|
-
this._quotesPulseProvider.unsubscribeQuotes(listenerGuid);
|
|
45
|
-
}
|
|
46
|
-
getMarks(symbolInfo, from, to, onDataCallback, resolution) {
|
|
47
|
-
if (!this._configuration.supports_marks) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const requestParams = {
|
|
51
|
-
symbol: symbolInfo.ticker || '',
|
|
52
|
-
from: from,
|
|
53
|
-
to: to,
|
|
54
|
-
resolution: resolution,
|
|
55
|
-
};
|
|
56
|
-
this._send('marks', requestParams)
|
|
57
|
-
.then((response) => {
|
|
58
|
-
if (!Array.isArray(response)) {
|
|
59
|
-
const result = [];
|
|
60
|
-
for (let i = 0; i < response.id.length; ++i) {
|
|
61
|
-
result.push({
|
|
62
|
-
id: extractField(response, 'id', i),
|
|
63
|
-
time: extractField(response, 'time', i),
|
|
64
|
-
color: extractField(response, 'color', i),
|
|
65
|
-
text: extractField(response, 'text', i),
|
|
66
|
-
label: extractField(response, 'label', i),
|
|
67
|
-
labelFontColor: extractField(response, 'labelFontColor', i),
|
|
68
|
-
minSize: extractField(response, 'minSize', i),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
response = result;
|
|
72
|
-
}
|
|
73
|
-
onDataCallback(response);
|
|
74
|
-
})
|
|
75
|
-
.catch((error) => {
|
|
76
|
-
logMessage(`UdfCompatibleDatafeed: Request marks failed: ${getErrorMessage(error)}`);
|
|
77
|
-
onDataCallback([]);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
getTimescaleMarks(symbolInfo, from, to, onDataCallback, resolution) {
|
|
81
|
-
if (!this._configuration.supports_timescale_marks) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const requestParams = {
|
|
85
|
-
symbol: symbolInfo.ticker || '',
|
|
86
|
-
from: from,
|
|
87
|
-
to: to,
|
|
88
|
-
resolution: resolution,
|
|
89
|
-
};
|
|
90
|
-
this._send('timescale_marks', requestParams)
|
|
91
|
-
.then((response) => {
|
|
92
|
-
if (!Array.isArray(response)) {
|
|
93
|
-
const result = [];
|
|
94
|
-
for (let i = 0; i < response.id.length; ++i) {
|
|
95
|
-
result.push({
|
|
96
|
-
id: extractField(response, 'id', i),
|
|
97
|
-
time: extractField(response, 'time', i),
|
|
98
|
-
color: extractField(response, 'color', i),
|
|
99
|
-
label: extractField(response, 'label', i),
|
|
100
|
-
tooltip: extractField(response, 'tooltip', i),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
response = result;
|
|
104
|
-
}
|
|
105
|
-
onDataCallback(response);
|
|
106
|
-
})
|
|
107
|
-
.catch((error) => {
|
|
108
|
-
logMessage(`UdfCompatibleDatafeed: Request timescale marks failed: ${getErrorMessage(error)}`);
|
|
109
|
-
onDataCallback([]);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
getServerTime(callback) {
|
|
113
|
-
if (!this._configuration.supports_time) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
this._send('time')
|
|
117
|
-
.then((response) => {
|
|
118
|
-
const time = parseInt(response);
|
|
119
|
-
if (!isNaN(time)) {
|
|
120
|
-
callback(time);
|
|
121
|
-
}
|
|
122
|
-
})
|
|
123
|
-
.catch((error) => {
|
|
124
|
-
logMessage(`UdfCompatibleDatafeed: Fail to load server time, error=${getErrorMessage(error)}`);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
searchSymbols(userInput, exchange, symbolType, onResult) {
|
|
128
|
-
if (this._configuration.supports_search) {
|
|
129
|
-
const params = {
|
|
130
|
-
limit: 30 /* SearchItemsLimit */,
|
|
131
|
-
query: userInput.toUpperCase(),
|
|
132
|
-
type: symbolType,
|
|
133
|
-
exchange: exchange,
|
|
134
|
-
};
|
|
135
|
-
this._send('search', params)
|
|
136
|
-
.then((response) => {
|
|
137
|
-
if (response.s !== undefined) {
|
|
138
|
-
logMessage(`UdfCompatibleDatafeed: search symbols error=${response.errmsg}`);
|
|
139
|
-
onResult([]);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
onResult(response);
|
|
143
|
-
})
|
|
144
|
-
.catch((reason) => {
|
|
145
|
-
logMessage(`UdfCompatibleDatafeed: Search symbols for '${userInput}' failed. Error=${getErrorMessage(reason)}`);
|
|
146
|
-
onResult([]);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
if (this._symbolsStorage === null) {
|
|
151
|
-
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
|
|
152
|
-
}
|
|
153
|
-
this._symbolsStorage.searchSymbols(userInput, exchange, symbolType, 30 /* SearchItemsLimit */)
|
|
154
|
-
.then(onResult)
|
|
155
|
-
.catch(onResult.bind(null, []));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
resolveSymbol(symbolName, onResolve, onError, extension) {
|
|
159
|
-
logMessage('Resolve requested');
|
|
160
|
-
const currencyCode = extension && extension.currencyCode;
|
|
161
|
-
const unitId = extension && extension.unitId;
|
|
162
|
-
const resolveRequestStartTime = Date.now();
|
|
163
|
-
function onResultReady(symbolInfo) {
|
|
164
|
-
logMessage(`Symbol resolved: ${Date.now() - resolveRequestStartTime}ms`);
|
|
165
|
-
onResolve(symbolInfo);
|
|
166
|
-
}
|
|
167
|
-
if (!this._configuration.supports_group_request) {
|
|
168
|
-
const params = {
|
|
169
|
-
symbol: symbolName,
|
|
170
|
-
};
|
|
171
|
-
if (currencyCode !== undefined) {
|
|
172
|
-
params.currencyCode = currencyCode;
|
|
173
|
-
}
|
|
174
|
-
if (unitId !== undefined) {
|
|
175
|
-
params.unitId = unitId;
|
|
176
|
-
}
|
|
177
|
-
this._send('symbols', params)
|
|
178
|
-
.then((response) => {
|
|
179
|
-
if (response.s !== undefined) {
|
|
180
|
-
onError('unknown_symbol');
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
onResultReady(response);
|
|
184
|
-
}
|
|
185
|
-
})
|
|
186
|
-
.catch((reason) => {
|
|
187
|
-
logMessage(`UdfCompatibleDatafeed: Error resolving symbol: ${getErrorMessage(reason)}`);
|
|
188
|
-
onError('unknown_symbol');
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
if (this._symbolsStorage === null) {
|
|
193
|
-
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
|
|
194
|
-
}
|
|
195
|
-
this._symbolsStorage.resolveSymbol(symbolName, currencyCode, unitId).then(onResultReady).catch(onError);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
getBars(symbolInfo, resolution, periodParams, onResult, onError) {
|
|
199
|
-
this._historyProvider.getBars(symbolInfo, resolution, periodParams)
|
|
200
|
-
.then((result) => {
|
|
201
|
-
onResult(result.bars, result.meta);
|
|
202
|
-
})
|
|
203
|
-
.catch(onError);
|
|
204
|
-
}
|
|
205
|
-
subscribeBars(symbolInfo, resolution, onTick, listenerGuid, onResetCacheNeededCallback) {
|
|
206
|
-
this._dataPulseProvider.subscribeBars(symbolInfo, resolution, onTick, listenerGuid);
|
|
207
|
-
}
|
|
208
|
-
unsubscribeBars(listenerGuid) {
|
|
209
|
-
this._dataPulseProvider.unsubscribeBars(listenerGuid);
|
|
210
|
-
}
|
|
211
|
-
_requestConfiguration() {
|
|
212
|
-
return this._send('config')
|
|
213
|
-
.catch((reason) => {
|
|
214
|
-
logMessage(`UdfCompatibleDatafeed: Cannot get datafeed configuration - use default, error=${getErrorMessage(reason)}`);
|
|
215
|
-
return null;
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
_send(urlPath, params) {
|
|
219
|
-
return this._requester.sendRequest(this._datafeedURL, urlPath, params);
|
|
220
|
-
}
|
|
221
|
-
_setupWithConfiguration(configurationData) {
|
|
222
|
-
this._configuration = configurationData;
|
|
223
|
-
if (configurationData.exchanges === undefined) {
|
|
224
|
-
configurationData.exchanges = [];
|
|
225
|
-
}
|
|
226
|
-
if (!configurationData.supports_search && !configurationData.supports_group_request) {
|
|
227
|
-
throw new Error('Unsupported datafeed configuration. Must either support search, or support group request');
|
|
228
|
-
}
|
|
229
|
-
if (configurationData.supports_group_request || !configurationData.supports_search) {
|
|
230
|
-
this._symbolsStorage = new SymbolsStorage(this._datafeedURL, configurationData.supported_resolutions || [], this._requester);
|
|
231
|
-
}
|
|
232
|
-
logMessage(`UdfCompatibleDatafeed: Initialized with ${JSON.stringify(configurationData)}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function defaultConfiguration() {
|
|
236
|
-
return {
|
|
237
|
-
supports_search: false,
|
|
238
|
-
supports_group_request: true,
|
|
239
|
-
supported_resolutions: [
|
|
240
|
-
'1',
|
|
241
|
-
'5',
|
|
242
|
-
'15',
|
|
243
|
-
'30',
|
|
244
|
-
'60',
|
|
245
|
-
'1D',
|
|
246
|
-
'1W',
|
|
247
|
-
'1M',
|
|
248
|
-
],
|
|
249
|
-
supports_marks: false,
|
|
250
|
-
supports_timescale_marks: false,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { UDFCompatibleDatafeedBase } from './udf-compatible-datafeed-base';
|
|
2
|
-
import { QuotesProvider } from './quotes-provider';
|
|
3
|
-
import { Requester } from './requester';
|
|
4
|
-
export class UDFCompatibleDatafeed extends UDFCompatibleDatafeedBase {
|
|
5
|
-
constructor(datafeedURL, updateFrequency = 10 * 1000) {
|
|
6
|
-
const requester = new Requester();
|
|
7
|
-
const quotesProvider = new QuotesProvider(datafeedURL, requester);
|
|
8
|
-
super(datafeedURL, quotesProvider, requester, updateFrequency);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"private": true,
|
|
3
|
-
"dependencies": {
|
|
4
|
-
"tslib": "2.3.0"
|
|
5
|
-
},
|
|
6
|
-
"devDependencies": {
|
|
7
|
-
"@rollup/plugin-node-resolve": "~9.0.0",
|
|
8
|
-
"rollup": "~2.28.2",
|
|
9
|
-
"rollup-plugin-terser": "~7.0.2",
|
|
10
|
-
"typescript": "4.3.5"
|
|
11
|
-
},
|
|
12
|
-
"scripts": {
|
|
13
|
-
"compile": "tsc",
|
|
14
|
-
"bundle-js": "rollup -c rollup.config.js",
|
|
15
|
-
"build": "npm run compile && npm run bundle-js"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/* globals process */
|
|
2
|
-
|
|
3
|
-
import { terser } from 'rollup-plugin-terser';
|
|
4
|
-
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
5
|
-
|
|
6
|
-
const environment = process.env.ENV || 'development';
|
|
7
|
-
const isDevelopmentEnv = (environment === 'development');
|
|
8
|
-
|
|
9
|
-
export default [
|
|
10
|
-
{
|
|
11
|
-
input: 'lib/udf-compatible-datafeed.js',
|
|
12
|
-
output: {
|
|
13
|
-
name: 'Datafeeds',
|
|
14
|
-
format: 'umd',
|
|
15
|
-
file: 'dist/bundle.js',
|
|
16
|
-
},
|
|
17
|
-
plugins: [
|
|
18
|
-
nodeResolve(),
|
|
19
|
-
!isDevelopmentEnv && terser({
|
|
20
|
-
ecma: 2018,
|
|
21
|
-
output: { inline_script: true },
|
|
22
|
-
}),
|
|
23
|
-
],
|
|
24
|
-
},
|
|
25
|
-
];
|