binbot-charts 0.0.21 → 0.0.22
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 +24 -32
- package/dist/components/datafeed.js +131 -100
- package/dist/components/streaming.js +1 -1
- package/package.json +2 -2
- 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,298 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LibrarySymbolInfo,
|
|
3
|
-
SearchSymbolResultItem,
|
|
4
|
-
ResolutionString,
|
|
5
|
-
} from '../../../charting_library/datafeed-api';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
getErrorMessage,
|
|
9
|
-
logMessage,
|
|
10
|
-
} from './helpers';
|
|
11
|
-
|
|
12
|
-
import { Requester } from './requester';
|
|
13
|
-
|
|
14
|
-
interface SymbolInfoMap {
|
|
15
|
-
[symbol: string]: LibrarySymbolInfo | undefined;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ExchangeDataResponseSymbolData {
|
|
19
|
-
'type': string;
|
|
20
|
-
'timezone': LibrarySymbolInfo['timezone'];
|
|
21
|
-
'description': string;
|
|
22
|
-
|
|
23
|
-
'exchange-listed': string;
|
|
24
|
-
'exchange-traded': string;
|
|
25
|
-
|
|
26
|
-
'session-regular': string;
|
|
27
|
-
'corrections'?: string;
|
|
28
|
-
'session-holidays'?: string;
|
|
29
|
-
|
|
30
|
-
'fractional': boolean;
|
|
31
|
-
|
|
32
|
-
'pricescale': number;
|
|
33
|
-
|
|
34
|
-
'ticker'?: string;
|
|
35
|
-
|
|
36
|
-
'minmov2'?: number;
|
|
37
|
-
'minmove2'?: number;
|
|
38
|
-
|
|
39
|
-
'minmov'?: number;
|
|
40
|
-
'minmovement'?: number;
|
|
41
|
-
|
|
42
|
-
'supported-resolutions'?: ResolutionString[];
|
|
43
|
-
'intraday-multipliers'?: string[];
|
|
44
|
-
|
|
45
|
-
'has-intraday'?: boolean;
|
|
46
|
-
'has-daily'?: boolean;
|
|
47
|
-
'has-weekly-and-monthly'?: boolean;
|
|
48
|
-
'has-empty-bars'?: boolean;
|
|
49
|
-
'has-no-volume'?: boolean;
|
|
50
|
-
'currency-code'?: string;
|
|
51
|
-
'original-currency-code'?: string;
|
|
52
|
-
'unit-id'?: string;
|
|
53
|
-
'original-unit-id'?: string;
|
|
54
|
-
'unit-conversion-types'?: string[];
|
|
55
|
-
|
|
56
|
-
'volume-precision'?: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Here is some black magic with types to get compile-time checks of names and types
|
|
60
|
-
type PickArrayedObjectFields<T> = Pick<T, {
|
|
61
|
-
// tslint:disable-next-line:no-any
|
|
62
|
-
[K in keyof T]-?: NonNullable<T[K]> extends any[] ? K : never;
|
|
63
|
-
}[keyof T]>;
|
|
64
|
-
|
|
65
|
-
type ExchangeDataResponseArrayedSymbolData = PickArrayedObjectFields<ExchangeDataResponseSymbolData>;
|
|
66
|
-
type ExchangeDataResponseNonArrayedSymbolData = Pick<ExchangeDataResponseSymbolData, Exclude<keyof ExchangeDataResponseSymbolData, keyof ExchangeDataResponseArrayedSymbolData>>;
|
|
67
|
-
|
|
68
|
-
type ExchangeDataResponse =
|
|
69
|
-
{
|
|
70
|
-
symbol: string[];
|
|
71
|
-
} &
|
|
72
|
-
{
|
|
73
|
-
[K in keyof ExchangeDataResponseSymbolData]: ExchangeDataResponseSymbolData[K] | NonNullable<ExchangeDataResponseSymbolData[K]>[];
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
function extractField<Field extends keyof ExchangeDataResponseNonArrayedSymbolData>(data: ExchangeDataResponse, field: Field, arrayIndex: number): ExchangeDataResponseNonArrayedSymbolData[Field];
|
|
77
|
-
function extractField<Field extends keyof ExchangeDataResponseArrayedSymbolData>(data: ExchangeDataResponse, field: Field, arrayIndex: number, valueIsArray: true): ExchangeDataResponseArrayedSymbolData[Field];
|
|
78
|
-
function extractField<Field extends keyof ExchangeDataResponseSymbolData>(data: ExchangeDataResponse, field: Field, arrayIndex: number, valueIsArray?: boolean): ExchangeDataResponseSymbolData[Field] {
|
|
79
|
-
const value: ExchangeDataResponse[keyof ExchangeDataResponseSymbolData] = data[field];
|
|
80
|
-
|
|
81
|
-
if (Array.isArray(value) && (!valueIsArray || Array.isArray(value[0]))) {
|
|
82
|
-
return value[arrayIndex] as ExchangeDataResponseSymbolData[Field];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return value as ExchangeDataResponseSymbolData[Field];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function symbolKey(symbol: string, currency?: string, unit?: string): string {
|
|
89
|
-
// here we're using a separator that quite possible shouldn't be in a real symbol name
|
|
90
|
-
return symbol + (currency !== undefined ? '_%|#|%_' + currency : '') + (unit !== undefined ? '_%|#|%_' + unit : '');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export class SymbolsStorage {
|
|
94
|
-
private readonly _exchangesList: string[] = ['NYSE', 'FOREX', 'AMEX'];
|
|
95
|
-
private readonly _symbolsInfo: SymbolInfoMap = {};
|
|
96
|
-
private readonly _symbolsList: string[] = [];
|
|
97
|
-
private readonly _datafeedUrl: string;
|
|
98
|
-
private readonly _readyPromise: Promise<void>;
|
|
99
|
-
private readonly _datafeedSupportedResolutions: ResolutionString[];
|
|
100
|
-
private readonly _requester: Requester;
|
|
101
|
-
|
|
102
|
-
public constructor(datafeedUrl: string, datafeedSupportedResolutions: ResolutionString[], requester: Requester) {
|
|
103
|
-
this._datafeedUrl = datafeedUrl;
|
|
104
|
-
this._datafeedSupportedResolutions = datafeedSupportedResolutions;
|
|
105
|
-
this._requester = requester;
|
|
106
|
-
this._readyPromise = this._init();
|
|
107
|
-
this._readyPromise.catch((error: Error) => {
|
|
108
|
-
// seems it is impossible
|
|
109
|
-
// tslint:disable-next-line:no-console
|
|
110
|
-
console.error(`SymbolsStorage: Cannot init, error=${error.toString()}`);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// BEWARE: this function does not consider symbol's exchange
|
|
115
|
-
public resolveSymbol(symbolName: string, currencyCode?: string, unitId?: string): Promise<LibrarySymbolInfo> {
|
|
116
|
-
return this._readyPromise.then(() => {
|
|
117
|
-
const symbolInfo = this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)];
|
|
118
|
-
if (symbolInfo === undefined) {
|
|
119
|
-
return Promise.reject('invalid symbol');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return Promise.resolve(symbolInfo);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
public searchSymbols(searchString: string, exchange: string, symbolType: string, maxSearchResults: number): Promise<SearchSymbolResultItem[]> {
|
|
127
|
-
interface WeightedItem {
|
|
128
|
-
symbolInfo: LibrarySymbolInfo;
|
|
129
|
-
weight: number;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return this._readyPromise.then(() => {
|
|
133
|
-
const weightedResult: WeightedItem[] = [];
|
|
134
|
-
const queryIsEmpty = searchString.length === 0;
|
|
135
|
-
|
|
136
|
-
searchString = searchString.toUpperCase();
|
|
137
|
-
|
|
138
|
-
for (const symbolName of this._symbolsList) {
|
|
139
|
-
const symbolInfo = this._symbolsInfo[symbolName];
|
|
140
|
-
|
|
141
|
-
if (symbolInfo === undefined) {
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (symbolType.length > 0 && symbolInfo.type !== symbolType) {
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (exchange && exchange.length > 0 && symbolInfo.exchange !== exchange) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const positionInName = symbolInfo.name.toUpperCase().indexOf(searchString);
|
|
154
|
-
const positionInDescription = symbolInfo.description.toUpperCase().indexOf(searchString);
|
|
155
|
-
|
|
156
|
-
if (queryIsEmpty || positionInName >= 0 || positionInDescription >= 0) {
|
|
157
|
-
const alreadyExists = weightedResult.some((item: WeightedItem) => item.symbolInfo === symbolInfo);
|
|
158
|
-
if (!alreadyExists) {
|
|
159
|
-
const weight = positionInName >= 0 ? positionInName : 8000 + positionInDescription;
|
|
160
|
-
weightedResult.push({ symbolInfo: symbolInfo, weight: weight });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const result = weightedResult
|
|
166
|
-
.sort((item1: WeightedItem, item2: WeightedItem) => item1.weight - item2.weight)
|
|
167
|
-
.slice(0, maxSearchResults)
|
|
168
|
-
.map((item: WeightedItem) => {
|
|
169
|
-
const symbolInfo = item.symbolInfo;
|
|
170
|
-
return {
|
|
171
|
-
symbol: symbolInfo.name,
|
|
172
|
-
full_name: symbolInfo.full_name,
|
|
173
|
-
description: symbolInfo.description,
|
|
174
|
-
exchange: symbolInfo.exchange,
|
|
175
|
-
params: [],
|
|
176
|
-
type: symbolInfo.type,
|
|
177
|
-
ticker: symbolInfo.name,
|
|
178
|
-
};
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return Promise.resolve(result);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private _init(): Promise<void> {
|
|
186
|
-
interface BooleanMap {
|
|
187
|
-
[key: string]: boolean | undefined;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const promises: Promise<void>[] = [];
|
|
191
|
-
const alreadyRequestedExchanges: BooleanMap = {};
|
|
192
|
-
|
|
193
|
-
for (const exchange of this._exchangesList) {
|
|
194
|
-
if (alreadyRequestedExchanges[exchange]) {
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
alreadyRequestedExchanges[exchange] = true;
|
|
199
|
-
promises.push(this._requestExchangeData(exchange));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return Promise.all(promises)
|
|
203
|
-
.then(() => {
|
|
204
|
-
this._symbolsList.sort();
|
|
205
|
-
logMessage('SymbolsStorage: All exchanges data loaded');
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
private _requestExchangeData(exchange: string): Promise<void> {
|
|
210
|
-
return new Promise((resolve: () => void, reject: (error: Error) => void) => {
|
|
211
|
-
this._requester.sendRequest<ExchangeDataResponse>(this._datafeedUrl, 'symbol_info', { group: exchange })
|
|
212
|
-
.then((response: ExchangeDataResponse) => {
|
|
213
|
-
try {
|
|
214
|
-
this._onExchangeDataReceived(exchange, response);
|
|
215
|
-
} catch (error) {
|
|
216
|
-
reject(error);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
resolve();
|
|
221
|
-
})
|
|
222
|
-
.catch((reason?: string | Error) => {
|
|
223
|
-
logMessage(`SymbolsStorage: Request data for exchange '${exchange}' failed, reason=${getErrorMessage(reason)}`);
|
|
224
|
-
resolve();
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private _onExchangeDataReceived(exchange: string, data: ExchangeDataResponse): void {
|
|
230
|
-
let symbolIndex = 0;
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
const symbolsCount = data.symbol.length;
|
|
234
|
-
const tickerPresent = data.ticker !== undefined;
|
|
235
|
-
|
|
236
|
-
for (; symbolIndex < symbolsCount; ++symbolIndex) {
|
|
237
|
-
const symbolName = data.symbol[symbolIndex];
|
|
238
|
-
const listedExchange = extractField(data, 'exchange-listed', symbolIndex);
|
|
239
|
-
const tradedExchange = extractField(data, 'exchange-traded', symbolIndex);
|
|
240
|
-
const fullName = tradedExchange + ':' + symbolName;
|
|
241
|
-
const currencyCode = extractField(data, 'currency-code', symbolIndex);
|
|
242
|
-
const unitId = extractField(data, 'unit-id', symbolIndex);
|
|
243
|
-
|
|
244
|
-
const ticker = tickerPresent ? (extractField(data, 'ticker', symbolIndex) as string) : symbolName;
|
|
245
|
-
|
|
246
|
-
const symbolInfo: LibrarySymbolInfo = {
|
|
247
|
-
ticker: ticker,
|
|
248
|
-
name: symbolName,
|
|
249
|
-
base_name: [listedExchange + ':' + symbolName],
|
|
250
|
-
full_name: fullName,
|
|
251
|
-
listed_exchange: listedExchange,
|
|
252
|
-
exchange: tradedExchange,
|
|
253
|
-
currency_code: currencyCode,
|
|
254
|
-
original_currency_code: extractField(data, 'original-currency-code', symbolIndex),
|
|
255
|
-
unit_id: unitId,
|
|
256
|
-
original_unit_id: extractField(data, 'original-unit-id', symbolIndex),
|
|
257
|
-
unit_conversion_types: extractField(data, 'unit-conversion-types', symbolIndex, true),
|
|
258
|
-
description: extractField(data, 'description', symbolIndex),
|
|
259
|
-
has_intraday: definedValueOrDefault(extractField(data, 'has-intraday', symbolIndex), false),
|
|
260
|
-
has_no_volume: definedValueOrDefault(extractField(data, 'has-no-volume', symbolIndex), false),
|
|
261
|
-
minmov: extractField(data, 'minmovement', symbolIndex) || extractField(data, 'minmov', symbolIndex) || 0,
|
|
262
|
-
minmove2: extractField(data, 'minmove2', symbolIndex) || extractField(data, 'minmov2', symbolIndex),
|
|
263
|
-
fractional: extractField(data, 'fractional', symbolIndex),
|
|
264
|
-
pricescale: extractField(data, 'pricescale', symbolIndex),
|
|
265
|
-
type: extractField(data, 'type', symbolIndex),
|
|
266
|
-
session: extractField(data, 'session-regular', symbolIndex),
|
|
267
|
-
session_holidays: extractField(data, 'session-holidays', symbolIndex),
|
|
268
|
-
corrections: extractField(data, 'corrections', symbolIndex),
|
|
269
|
-
timezone: extractField(data, 'timezone', symbolIndex),
|
|
270
|
-
supported_resolutions: definedValueOrDefault(extractField(data, 'supported-resolutions', symbolIndex, true), this._datafeedSupportedResolutions),
|
|
271
|
-
has_daily: definedValueOrDefault(extractField(data, 'has-daily', symbolIndex), true),
|
|
272
|
-
intraday_multipliers: definedValueOrDefault(extractField(data, 'intraday-multipliers', symbolIndex, true), ['1', '5', '15', '30', '60']),
|
|
273
|
-
has_weekly_and_monthly: extractField(data, 'has-weekly-and-monthly', symbolIndex),
|
|
274
|
-
has_empty_bars: extractField(data, 'has-empty-bars', symbolIndex),
|
|
275
|
-
volume_precision: definedValueOrDefault(extractField(data, 'volume-precision', symbolIndex), 0),
|
|
276
|
-
format: 'price',
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
this._symbolsInfo[ticker] = symbolInfo;
|
|
280
|
-
this._symbolsInfo[symbolName] = symbolInfo;
|
|
281
|
-
this._symbolsInfo[fullName] = symbolInfo;
|
|
282
|
-
if (currencyCode !== undefined || unitId !== undefined) {
|
|
283
|
-
this._symbolsInfo[symbolKey(ticker, currencyCode, unitId)] = symbolInfo;
|
|
284
|
-
this._symbolsInfo[symbolKey(symbolName, currencyCode, unitId)] = symbolInfo;
|
|
285
|
-
this._symbolsInfo[symbolKey(fullName, currencyCode, unitId)] = symbolInfo;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
this._symbolsList.push(symbolName);
|
|
289
|
-
}
|
|
290
|
-
} catch (error) {
|
|
291
|
-
throw new Error(`SymbolsStorage: API error when processing exchange ${exchange} symbol #${symbolIndex} (${data.symbol[symbolIndex]}): ${error.message}`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function definedValueOrDefault<T>(value: T | undefined, defaultValue: T): T {
|
|
297
|
-
return value !== undefined ? value : defaultValue;
|
|
298
|
-
}
|
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DatafeedConfiguration,
|
|
3
|
-
ErrorCallback,
|
|
4
|
-
GetMarksCallback,
|
|
5
|
-
HistoryCallback,
|
|
6
|
-
IDatafeedChartApi,
|
|
7
|
-
IDatafeedQuotesApi,
|
|
8
|
-
IExternalDatafeed,
|
|
9
|
-
LibrarySymbolInfo,
|
|
10
|
-
Mark,
|
|
11
|
-
OnReadyCallback,
|
|
12
|
-
QuotesCallback,
|
|
13
|
-
ResolutionString,
|
|
14
|
-
ResolveCallback,
|
|
15
|
-
SearchSymbolResultItem,
|
|
16
|
-
SearchSymbolsCallback,
|
|
17
|
-
ServerTimeCallback,
|
|
18
|
-
SubscribeBarsCallback,
|
|
19
|
-
TimescaleMark,
|
|
20
|
-
SymbolResolveExtension,
|
|
21
|
-
} from '../../../charting_library/datafeed-api';
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
getErrorMessage,
|
|
25
|
-
logMessage,
|
|
26
|
-
RequestParams,
|
|
27
|
-
UdfErrorResponse,
|
|
28
|
-
} from './helpers';
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
GetBarsResult,
|
|
32
|
-
HistoryProvider,
|
|
33
|
-
PeriodParamsWithOptionalCountback,
|
|
34
|
-
} from './history-provider';
|
|
35
|
-
|
|
36
|
-
import { IQuotesProvider } from './iquotes-provider';
|
|
37
|
-
import { DataPulseProvider } from './data-pulse-provider';
|
|
38
|
-
import { QuotesPulseProvider } from './quotes-pulse-provider';
|
|
39
|
-
import { SymbolsStorage } from './symbols-storage';
|
|
40
|
-
import { Requester } from './requester';
|
|
41
|
-
|
|
42
|
-
export interface UdfCompatibleConfiguration extends DatafeedConfiguration {
|
|
43
|
-
// tslint:disable:tv-variable-name
|
|
44
|
-
supports_search?: boolean;
|
|
45
|
-
supports_group_request?: boolean;
|
|
46
|
-
// tslint:enable:tv-variable-name
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface ResolveSymbolResponse extends LibrarySymbolInfo {
|
|
50
|
-
s: undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// it is hack to let's TypeScript make code flow analysis
|
|
54
|
-
export interface UdfSearchSymbolsResponse extends Array<SearchSymbolResultItem> {
|
|
55
|
-
s?: undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const enum Constants {
|
|
59
|
-
SearchItemsLimit = 30,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
type UdfDatafeedMarkType<T extends TimescaleMark | Mark> = {
|
|
63
|
-
[K in keyof T]: T[K] | T[K][];
|
|
64
|
-
} & {
|
|
65
|
-
id: (string | number)[];
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
type UdfDatafeedMark = UdfDatafeedMarkType<Mark>;
|
|
69
|
-
type UdfDatafeedTimescaleMark = UdfDatafeedMarkType<TimescaleMark>;
|
|
70
|
-
|
|
71
|
-
function extractField<Field extends keyof Mark>(data: UdfDatafeedMark, field: Field, arrayIndex: number): Mark[Field];
|
|
72
|
-
function extractField<Field extends keyof TimescaleMark>(data: UdfDatafeedTimescaleMark, field: Field, arrayIndex: number): TimescaleMark[Field];
|
|
73
|
-
function extractField<T, TField extends keyof T>(data: T, field: TField, arrayIndex: number): T[TField] {
|
|
74
|
-
const value = data[field];
|
|
75
|
-
return Array.isArray(value) ? value[arrayIndex] : value;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* This class implements interaction with UDF-compatible datafeed.
|
|
80
|
-
* See UDF protocol reference at https://github.com/tradingview/charting_library/wiki/UDF
|
|
81
|
-
*/
|
|
82
|
-
export class UDFCompatibleDatafeedBase implements IExternalDatafeed, IDatafeedQuotesApi, IDatafeedChartApi {
|
|
83
|
-
protected _configuration: UdfCompatibleConfiguration = defaultConfiguration();
|
|
84
|
-
private readonly _datafeedURL: string;
|
|
85
|
-
private readonly _configurationReadyPromise: Promise<void>;
|
|
86
|
-
|
|
87
|
-
private _symbolsStorage: SymbolsStorage | null = null;
|
|
88
|
-
|
|
89
|
-
private readonly _historyProvider: HistoryProvider;
|
|
90
|
-
private readonly _dataPulseProvider: DataPulseProvider;
|
|
91
|
-
|
|
92
|
-
private readonly _quotesProvider: IQuotesProvider;
|
|
93
|
-
private readonly _quotesPulseProvider: QuotesPulseProvider;
|
|
94
|
-
|
|
95
|
-
private readonly _requester: Requester;
|
|
96
|
-
|
|
97
|
-
protected constructor(datafeedURL: string, quotesProvider: IQuotesProvider, requester: Requester, updateFrequency: number = 10 * 1000) {
|
|
98
|
-
this._datafeedURL = datafeedURL;
|
|
99
|
-
this._requester = requester;
|
|
100
|
-
this._historyProvider = new HistoryProvider(datafeedURL, this._requester);
|
|
101
|
-
this._quotesProvider = quotesProvider;
|
|
102
|
-
|
|
103
|
-
this._dataPulseProvider = new DataPulseProvider(this._historyProvider, updateFrequency);
|
|
104
|
-
this._quotesPulseProvider = new QuotesPulseProvider(this._quotesProvider);
|
|
105
|
-
|
|
106
|
-
this._configurationReadyPromise = this._requestConfiguration()
|
|
107
|
-
.then((configuration: UdfCompatibleConfiguration | null) => {
|
|
108
|
-
if (configuration === null) {
|
|
109
|
-
configuration = defaultConfiguration();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
this._setupWithConfiguration(configuration);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
public onReady(callback: OnReadyCallback): void {
|
|
117
|
-
this._configurationReadyPromise.then(() => {
|
|
118
|
-
callback(this._configuration);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
public getQuotes(symbols: string[], onDataCallback: QuotesCallback, onErrorCallback: (msg: string) => void): void {
|
|
123
|
-
this._quotesProvider.getQuotes(symbols).then(onDataCallback).catch(onErrorCallback);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
public subscribeQuotes(symbols: string[], fastSymbols: string[], onRealtimeCallback: QuotesCallback, listenerGuid: string): void {
|
|
127
|
-
this._quotesPulseProvider.subscribeQuotes(symbols, fastSymbols, onRealtimeCallback, listenerGuid);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public unsubscribeQuotes(listenerGuid: string): void {
|
|
131
|
-
this._quotesPulseProvider.unsubscribeQuotes(listenerGuid);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public getMarks(symbolInfo: LibrarySymbolInfo, from: number, to: number, onDataCallback: GetMarksCallback<Mark>, resolution: ResolutionString): void {
|
|
135
|
-
if (!this._configuration.supports_marks) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const requestParams: RequestParams = {
|
|
140
|
-
symbol: symbolInfo.ticker || '',
|
|
141
|
-
from: from,
|
|
142
|
-
to: to,
|
|
143
|
-
resolution: resolution,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
this._send<Mark[] | UdfDatafeedMark>('marks', requestParams)
|
|
147
|
-
.then((response: Mark[] | UdfDatafeedMark) => {
|
|
148
|
-
if (!Array.isArray(response)) {
|
|
149
|
-
const result: Mark[] = [];
|
|
150
|
-
for (let i = 0; i < response.id.length; ++i) {
|
|
151
|
-
result.push({
|
|
152
|
-
id: extractField(response, 'id', i),
|
|
153
|
-
time: extractField(response, 'time', i),
|
|
154
|
-
color: extractField(response, 'color', i),
|
|
155
|
-
text: extractField(response, 'text', i),
|
|
156
|
-
label: extractField(response, 'label', i),
|
|
157
|
-
labelFontColor: extractField(response, 'labelFontColor', i),
|
|
158
|
-
minSize: extractField(response, 'minSize', i),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
response = result;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
onDataCallback(response);
|
|
166
|
-
})
|
|
167
|
-
.catch((error?: string | Error) => {
|
|
168
|
-
logMessage(`UdfCompatibleDatafeed: Request marks failed: ${getErrorMessage(error)}`);
|
|
169
|
-
onDataCallback([]);
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
public getTimescaleMarks(symbolInfo: LibrarySymbolInfo, from: number, to: number, onDataCallback: GetMarksCallback<TimescaleMark>, resolution: ResolutionString): void {
|
|
174
|
-
if (!this._configuration.supports_timescale_marks) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const requestParams: RequestParams = {
|
|
179
|
-
symbol: symbolInfo.ticker || '',
|
|
180
|
-
from: from,
|
|
181
|
-
to: to,
|
|
182
|
-
resolution: resolution,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
this._send<TimescaleMark[] | UdfDatafeedTimescaleMark>('timescale_marks', requestParams)
|
|
186
|
-
.then((response: TimescaleMark[] | UdfDatafeedTimescaleMark) => {
|
|
187
|
-
if (!Array.isArray(response)) {
|
|
188
|
-
const result: TimescaleMark[] = [];
|
|
189
|
-
for (let i = 0; i < response.id.length; ++i) {
|
|
190
|
-
result.push({
|
|
191
|
-
id: extractField(response, 'id', i),
|
|
192
|
-
time: extractField(response, 'time', i),
|
|
193
|
-
color: extractField(response, 'color', i),
|
|
194
|
-
label: extractField(response, 'label', i),
|
|
195
|
-
tooltip: extractField(response, 'tooltip', i),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
response = result;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
onDataCallback(response);
|
|
203
|
-
})
|
|
204
|
-
.catch((error?: string | Error) => {
|
|
205
|
-
logMessage(`UdfCompatibleDatafeed: Request timescale marks failed: ${getErrorMessage(error)}`);
|
|
206
|
-
onDataCallback([]);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
public getServerTime(callback: ServerTimeCallback): void {
|
|
211
|
-
if (!this._configuration.supports_time) {
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
this._send<string>('time')
|
|
216
|
-
.then((response: string) => {
|
|
217
|
-
const time = parseInt(response);
|
|
218
|
-
if (!isNaN(time)) {
|
|
219
|
-
callback(time);
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
.catch((error?: string | Error) => {
|
|
223
|
-
logMessage(`UdfCompatibleDatafeed: Fail to load server time, error=${getErrorMessage(error)}`);
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
public searchSymbols(userInput: string, exchange: string, symbolType: string, onResult: SearchSymbolsCallback): void {
|
|
228
|
-
if (this._configuration.supports_search) {
|
|
229
|
-
const params: RequestParams = {
|
|
230
|
-
limit: Constants.SearchItemsLimit,
|
|
231
|
-
query: userInput.toUpperCase(),
|
|
232
|
-
type: symbolType,
|
|
233
|
-
exchange: exchange,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
this._send<UdfSearchSymbolsResponse | UdfErrorResponse>('search', params)
|
|
237
|
-
.then((response: UdfSearchSymbolsResponse | UdfErrorResponse) => {
|
|
238
|
-
if (response.s !== undefined) {
|
|
239
|
-
logMessage(`UdfCompatibleDatafeed: search symbols error=${response.errmsg}`);
|
|
240
|
-
onResult([]);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
onResult(response);
|
|
245
|
-
})
|
|
246
|
-
.catch((reason?: string | Error) => {
|
|
247
|
-
logMessage(`UdfCompatibleDatafeed: Search symbols for '${userInput}' failed. Error=${getErrorMessage(reason)}`);
|
|
248
|
-
onResult([]);
|
|
249
|
-
});
|
|
250
|
-
} else {
|
|
251
|
-
if (this._symbolsStorage === null) {
|
|
252
|
-
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
this._symbolsStorage.searchSymbols(userInput, exchange, symbolType, Constants.SearchItemsLimit)
|
|
256
|
-
.then(onResult)
|
|
257
|
-
.catch(onResult.bind(null, []));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
public resolveSymbol(symbolName: string, onResolve: ResolveCallback, onError: ErrorCallback, extension?: SymbolResolveExtension): void {
|
|
262
|
-
logMessage('Resolve requested');
|
|
263
|
-
|
|
264
|
-
const currencyCode = extension && extension.currencyCode;
|
|
265
|
-
const unitId = extension && extension.unitId;
|
|
266
|
-
|
|
267
|
-
const resolveRequestStartTime = Date.now();
|
|
268
|
-
function onResultReady(symbolInfo: LibrarySymbolInfo): void {
|
|
269
|
-
logMessage(`Symbol resolved: ${Date.now() - resolveRequestStartTime}ms`);
|
|
270
|
-
onResolve(symbolInfo);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (!this._configuration.supports_group_request) {
|
|
274
|
-
const params: RequestParams = {
|
|
275
|
-
symbol: symbolName,
|
|
276
|
-
};
|
|
277
|
-
if (currencyCode !== undefined) {
|
|
278
|
-
params.currencyCode = currencyCode;
|
|
279
|
-
}
|
|
280
|
-
if (unitId !== undefined) {
|
|
281
|
-
params.unitId = unitId;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
this._send<ResolveSymbolResponse | UdfErrorResponse>('symbols', params)
|
|
285
|
-
.then((response: ResolveSymbolResponse | UdfErrorResponse) => {
|
|
286
|
-
if (response.s !== undefined) {
|
|
287
|
-
onError('unknown_symbol');
|
|
288
|
-
} else {
|
|
289
|
-
onResultReady(response);
|
|
290
|
-
}
|
|
291
|
-
})
|
|
292
|
-
.catch((reason?: string | Error) => {
|
|
293
|
-
logMessage(`UdfCompatibleDatafeed: Error resolving symbol: ${getErrorMessage(reason)}`);
|
|
294
|
-
onError('unknown_symbol');
|
|
295
|
-
});
|
|
296
|
-
} else {
|
|
297
|
-
if (this._symbolsStorage === null) {
|
|
298
|
-
throw new Error('UdfCompatibleDatafeed: inconsistent configuration (symbols storage)');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
this._symbolsStorage.resolveSymbol(symbolName, currencyCode, unitId).then(onResultReady).catch(onError);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
public getBars(symbolInfo: LibrarySymbolInfo, resolution: ResolutionString, periodParams: PeriodParamsWithOptionalCountback, onResult: HistoryCallback, onError: ErrorCallback): void {
|
|
306
|
-
this._historyProvider.getBars(symbolInfo, resolution, periodParams)
|
|
307
|
-
.then((result: GetBarsResult) => {
|
|
308
|
-
onResult(result.bars, result.meta);
|
|
309
|
-
})
|
|
310
|
-
.catch(onError);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
public subscribeBars(symbolInfo: LibrarySymbolInfo, resolution: ResolutionString, onTick: SubscribeBarsCallback, listenerGuid: string, onResetCacheNeededCallback: () => void): void {
|
|
314
|
-
this._dataPulseProvider.subscribeBars(symbolInfo, resolution, onTick, listenerGuid);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
public unsubscribeBars(listenerGuid: string): void {
|
|
318
|
-
this._dataPulseProvider.unsubscribeBars(listenerGuid);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
protected _requestConfiguration(): Promise<UdfCompatibleConfiguration | null> {
|
|
322
|
-
return this._send<UdfCompatibleConfiguration>('config')
|
|
323
|
-
.catch((reason?: string | Error) => {
|
|
324
|
-
logMessage(`UdfCompatibleDatafeed: Cannot get datafeed configuration - use default, error=${getErrorMessage(reason)}`);
|
|
325
|
-
return null;
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private _send<T>(urlPath: string, params?: RequestParams): Promise<T> {
|
|
330
|
-
return this._requester.sendRequest<T>(this._datafeedURL, urlPath, params);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
private _setupWithConfiguration(configurationData: UdfCompatibleConfiguration): void {
|
|
334
|
-
this._configuration = configurationData;
|
|
335
|
-
|
|
336
|
-
if (configurationData.exchanges === undefined) {
|
|
337
|
-
configurationData.exchanges = [];
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (!configurationData.supports_search && !configurationData.supports_group_request) {
|
|
341
|
-
throw new Error('Unsupported datafeed configuration. Must either support search, or support group request');
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (configurationData.supports_group_request || !configurationData.supports_search) {
|
|
345
|
-
this._symbolsStorage = new SymbolsStorage(this._datafeedURL, configurationData.supported_resolutions || [], this._requester);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
logMessage(`UdfCompatibleDatafeed: Initialized with ${JSON.stringify(configurationData)}`);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function defaultConfiguration(): UdfCompatibleConfiguration {
|
|
353
|
-
return {
|
|
354
|
-
supports_search: false,
|
|
355
|
-
supports_group_request: true,
|
|
356
|
-
supported_resolutions: [
|
|
357
|
-
'1' as ResolutionString,
|
|
358
|
-
'5' as ResolutionString,
|
|
359
|
-
'15' as ResolutionString,
|
|
360
|
-
'30' as ResolutionString,
|
|
361
|
-
'60' as ResolutionString,
|
|
362
|
-
'1D' as ResolutionString,
|
|
363
|
-
'1W' as ResolutionString,
|
|
364
|
-
'1M' as ResolutionString,
|
|
365
|
-
],
|
|
366
|
-
supports_marks: false,
|
|
367
|
-
supports_timescale_marks: false,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { UDFCompatibleDatafeedBase } from './udf-compatible-datafeed-base';
|
|
2
|
-
import { QuotesProvider } from './quotes-provider';
|
|
3
|
-
import { Requester } from './requester';
|
|
4
|
-
|
|
5
|
-
export class UDFCompatibleDatafeed extends UDFCompatibleDatafeedBase {
|
|
6
|
-
public constructor(datafeedURL: string, updateFrequency: number = 10 * 1000) {
|
|
7
|
-
const requester = new Requester();
|
|
8
|
-
const quotesProvider = new QuotesProvider(datafeedURL, requester);
|
|
9
|
-
super(datafeedURL, quotesProvider, requester, updateFrequency);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"allowSyntheticDefaultImports": true,
|
|
4
|
-
"importHelpers": true,
|
|
5
|
-
"lib": [
|
|
6
|
-
"dom",
|
|
7
|
-
"es2018"
|
|
8
|
-
],
|
|
9
|
-
"module": "es6",
|
|
10
|
-
"moduleResolution": "node",
|
|
11
|
-
"noEmitOnError": true,
|
|
12
|
-
"noFallthroughCasesInSwitch": true,
|
|
13
|
-
"noImplicitReturns": true,
|
|
14
|
-
"noUnusedLocals": true,
|
|
15
|
-
"outDir": "./lib/",
|
|
16
|
-
"rootDir": "src",
|
|
17
|
-
"sourceMap": false,
|
|
18
|
-
"strict": true,
|
|
19
|
-
"target": "es2018",
|
|
20
|
-
"types": []
|
|
21
|
-
},
|
|
22
|
-
"include": [
|
|
23
|
-
"./src/**/*.ts"
|
|
24
|
-
]
|
|
25
|
-
}
|