binbot-charts 0.0.20 → 0.0.21

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.
Files changed (31) hide show
  1. package/README.md +2 -0
  2. package/dist/components/TVChartContainer.js +45 -24
  3. package/dist/components/datafeed.js +2 -13
  4. package/dist/{index.js → components/index.js} +1 -1
  5. package/dist/datafeeds/README.md +3 -0
  6. package/dist/datafeeds/udf/README.md +46 -0
  7. package/dist/datafeeds/udf/dist/bundle.js +1 -0
  8. package/dist/datafeeds/udf/lib/data-pulse-provider.js +104 -0
  9. package/dist/datafeeds/udf/lib/helpers.js +20 -0
  10. package/dist/datafeeds/udf/lib/history-provider.js +73 -0
  11. package/dist/datafeeds/udf/lib/iquotes-provider.js +1 -0
  12. package/dist/datafeeds/udf/lib/quotes-provider.js +25 -0
  13. package/dist/datafeeds/udf/lib/quotes-pulse-provider.js +44 -0
  14. package/dist/datafeeds/udf/lib/requester.js +28 -0
  15. package/dist/datafeeds/udf/lib/symbols-storage.js +180 -0
  16. package/dist/datafeeds/udf/lib/udf-compatible-datafeed-base.js +252 -0
  17. package/dist/datafeeds/udf/lib/udf-compatible-datafeed.js +10 -0
  18. package/dist/datafeeds/udf/package.json +17 -0
  19. package/dist/datafeeds/udf/rollup.config.js +25 -0
  20. package/dist/datafeeds/udf/src/data-pulse-provider.ts +152 -0
  21. package/dist/datafeeds/udf/src/helpers.ts +38 -0
  22. package/dist/datafeeds/udf/src/history-provider.ts +134 -0
  23. package/dist/datafeeds/udf/src/iquotes-provider.ts +14 -0
  24. package/dist/datafeeds/udf/src/quotes-provider.ts +37 -0
  25. package/dist/datafeeds/udf/src/quotes-pulse-provider.ts +85 -0
  26. package/dist/datafeeds/udf/src/requester.ts +39 -0
  27. package/dist/datafeeds/udf/src/symbols-storage.ts +298 -0
  28. package/dist/datafeeds/udf/src/udf-compatible-datafeed-base.ts +369 -0
  29. package/dist/datafeeds/udf/src/udf-compatible-datafeed.ts +11 -0
  30. package/dist/datafeeds/udf/tsconfig.json +25 -0
  31. package/package.json +9 -6
@@ -0,0 +1,369 @@
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
+ }
@@ -0,0 +1,11 @@
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
+ }
@@ -0,0 +1,25 @@
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
+ }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.20",
2
+ "version": "0.0.21",
3
3
  "name": "binbot-charts",
4
4
  "dependencies": {
5
5
  "@babel/cli": "^7.18.10",
@@ -8,11 +8,12 @@
8
8
  "@babel/preset-env": "^7.18.10",
9
9
  "react": "^17.0.1",
10
10
  "react-dom": "^17.0.1",
11
- "react-scripts": "5.0.1"
11
+ "react-scripts": "5.0.1",
12
+ "socket.io-client": "^4.5.2"
12
13
  },
13
14
  "scripts": {
14
15
  "start": "react-scripts start",
15
- "build": "rm -rf dist && NODE_ENV=production babel src/lib/ -d dist && cp -r src/lib/charting_library dist/charting_library",
16
+ "build": "rm -rf dist && NODE_ENV=production babel src/components/ -d dist/components && cp -r src/charting_library dist/charting_library && cp -r public/datafeeds/ dist/datafeeds/",
16
17
  "publish": "yarn build && npm publish"
17
18
  },
18
19
  "description": "Binbot charts is the default candlestick bars chart used in terminal.binbot.com to render bots graphically.",
@@ -20,9 +21,11 @@
20
21
  "type": "git",
21
22
  "url": "git+https://github.com/carkod/binbot-charts.git"
22
23
  },
23
- "module": "dist/index.js",
24
- "main": "dist/index.js",
25
- "files": ["dist"],
24
+ "module": "dist/components/index.js",
25
+ "main": "dist/components/index.js",
26
+ "files": [
27
+ "dist"
28
+ ],
26
29
  "keywords": [
27
30
  "binbot",
28
31
  "charts"