@mathieuc/tradingview 2.0.5 → 3.0.0-pre

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.
@@ -0,0 +1,367 @@
1
+ const { genSessionID } = require('../utils');
2
+
3
+ const studyConstructor = require('./study');
4
+
5
+ /**
6
+ * @typedef {'HeikinAshi' | 'Renko' | 'LineBreak' | 'Kagi' | 'PointAndFigure'
7
+ * | 'Range'} ChartType Custom chart type
8
+ */
9
+
10
+ const ChartTypes = {
11
+ HeikinAshi: 'BarSetHeikenAshi@tv-basicstudies-60!',
12
+ Renko: 'BarSetRenko@tv-prostudies-40!',
13
+ LineBreak: 'BarSetPriceBreak@tv-prostudies-34!',
14
+ Kagi: 'BarSetKagi@tv-prostudies-34!',
15
+ PointAndFigure: 'BarSetPnF@tv-prostudies-34!',
16
+ Range: 'BarSetRange@tv-basicstudies-72!',
17
+ };
18
+
19
+ /**
20
+ * @typedef {Object} ChartInputs Custom chart type
21
+ * @prop {number} [atrLength] Renko/Kagi/PointAndFigure ATR length
22
+ * @prop {'open' | 'high' | 'low' | 'close' | 'hl2'
23
+ * | 'hlc3' | 'ohlc4'} [source] Renko/LineBreak/Kagi source
24
+ * @prop {'ATR' | string} [style] Renko/Kagi/PointAndFigure style
25
+ * @prop {number} [boxSize] Renko/PointAndFigure box size
26
+ * @prop {number} [reversalAmount] Kagi/PointAndFigure reversal amount
27
+ * @prop {'Close'} [sources] Renko/PointAndFigure sources
28
+ * @prop {boolean} [wicks] Renko wicks
29
+ * @prop {number} [lb] LineBreak Line break
30
+ * @prop {boolean} [oneStepBackBuilding] PointAndFigure oneStepBackBuilding
31
+ * @prop {boolean} [phantomBars] Range phantom bars
32
+ * @prop {boolean} [range] Range range
33
+ */
34
+
35
+ /** @typedef {Object<string, Function[]>} StudyListeners */
36
+
37
+ /**
38
+ * @typedef {Object} ChartSessionBridge
39
+ * @prop {string} sessionID
40
+ * @prop {StudyListeners} studyListeners
41
+ * @prop {Object<number, number>} indexes
42
+ * @prop {import('../client').SendPacket} send
43
+ */
44
+
45
+ /**
46
+ * @typedef {'seriesLoaded' | 'symbolLoaded' | 'update' | 'error'} ChartEvent
47
+ */
48
+
49
+ /**
50
+ * @typedef {Object} PricePeriod
51
+ * @prop {number} time Period timestamp
52
+ * @prop {number} open Period open value
53
+ * @prop {number} close Period close value
54
+ * @prop {number} max Period max value
55
+ * @prop {number} min Period min value
56
+ * @prop {number} change Period change absolute value
57
+ */
58
+
59
+ /**
60
+ * @typedef {Object} Subsession
61
+ * @prop {string} id Subsession ID (ex: 'regular')
62
+ * @prop {string} description Subsession description (ex: 'Regular')
63
+ * @prop {string} privat If private
64
+ * @prop {string} session Session (ex: '24x7')
65
+ * @prop {string} session-display Session display (ex: '24x7')
66
+ *
67
+ * @typedef {Object} MarketInfos
68
+ * @prop {string} series_id Used series (ex: 'ser_1')
69
+ * @prop {string} base_currency Base currency (ex: 'BTC')
70
+ * @prop {string} base_currency_id Base currency ID (ex: 'XTVCBTC')
71
+ * @prop {string} name Market short name (ex: 'BTCEUR')
72
+ * @prop {string} full_name Market full name (ex: 'COINBASE:BTCEUR')
73
+ * @prop {string} pro_name Market pro name (ex: 'COINBASE:BTCEUR')
74
+ * @prop {string} description Market symbol description (ex: 'BTC/EUR')
75
+ * @prop {string} short_description Market symbol short description (ex: 'BTC/EUR')
76
+ * @prop {string} exchange Market exchange (ex: 'COINBASE')
77
+ * @prop {string} listed_exchange Market exchange (ex: 'COINBASE')
78
+ * @prop {string} provider_id Values provider ID (ex: 'coinbase')
79
+ * @prop {string} currency_id Used currency ID (ex: 'EUR')
80
+ * @prop {string} currency_code Used currency code (ex: 'EUR')
81
+ * @prop {string} variable_tick_size Variable tick size
82
+ * @prop {number} pricescale Price scale
83
+ * @prop {number} pointvalue Point value
84
+ * @prop {string} session Session (ex: '24x7')
85
+ * @prop {string} session_display Session display (ex: '24x7')
86
+ * @prop {string} type Market type (ex: 'crypto')
87
+ * @prop {boolean} has_intraday If intraday values are available
88
+ * @prop {boolean} fractional If market is fractional
89
+ * @prop {boolean} is_tradable If the market is curently tradable
90
+ * @prop {number} minmov Minimum move value
91
+ * @prop {number} minmove2 Minimum move value 2
92
+ * @prop {string} timezone Used timezone
93
+ * @prop {boolean} is_replayable If the replay mode is available
94
+ * @prop {boolean} has_adjustment If the adjustment mode is enabled ????
95
+ * @prop {boolean} has_extended_hours Has extended hours
96
+ * @prop {string} bar_source Bar source
97
+ * @prop {string} bar_transform Bar transform
98
+ * @prop {boolean} bar_fillgaps Bar fill gaps
99
+ * @prop {string} allowed_adjustment Allowed adjustment (ex: 'none')
100
+ * @prop {string} subsession_id Subsession ID (ex: 'regular')
101
+ * @prop {string} pro_perm Pro permission (ex: '')
102
+ * @prop {[]} base_name Base name (ex: ['COINBASE:BTCEUR'])
103
+ * @prop {[]} legs Legs (ex: ['COINBASE:BTCEUR'])
104
+ * @prop {Subsession[]} subsessions Sub sessions
105
+ * @prop {[]} typespecs Typespecs (ex: [])
106
+ * @prop {[]} resolutions Resolutions (ex: [])
107
+ * @prop {[]} aliases Aliases (ex: [])
108
+ * @prop {[]} alternatives Alternatives (ex: [])
109
+ */
110
+
111
+ /**
112
+ * @param {import('../client').ClientBridge} client
113
+ */
114
+ module.exports = (client) => class ChartSession {
115
+ #sessionID = genSessionID('cs');
116
+
117
+ /** Parent client */
118
+ #client = client;
119
+
120
+ /** @type {StudyListeners} */
121
+ #studyListeners = {};
122
+
123
+ /**
124
+ * Table of periods values indexed by timestamp
125
+ * @type {Object<number, PricePeriod[]>}
126
+ */
127
+ #periods = {};
128
+
129
+ /** @return {PricePeriod[]} List of periods values */
130
+ get periods() {
131
+ return Object.values(this.#periods).sort((a, b) => b.time - a.time);
132
+ }
133
+
134
+ /**
135
+ * Current market infos
136
+ * @type {MarketInfos}
137
+ */
138
+ #infos = {};
139
+
140
+ /** @return {MarketInfos} Current market infos */
141
+ get infos() {
142
+ return this.#infos;
143
+ }
144
+
145
+ #callbacks = {
146
+ seriesLoaded: [],
147
+ symbolLoaded: [],
148
+ update: [],
149
+
150
+ event: [],
151
+ error: [],
152
+ };
153
+
154
+ /**
155
+ * @param {ChartEvent} ev Client event
156
+ * @param {...{}} data Packet data
157
+ */
158
+ #handleEvent(ev, ...data) {
159
+ this.#callbacks[ev].forEach((e) => e(...data));
160
+ this.#callbacks.event.forEach((e) => e(ev, ...data));
161
+ }
162
+
163
+ #handleError(...msgs) {
164
+ if (this.#callbacks.error.length === 0) console.error(...msgs);
165
+ else this.#handleEvent('error', ...msgs);
166
+ }
167
+
168
+ constructor() {
169
+ this.#client.sessions[this.#sessionID] = {
170
+ type: 'chart',
171
+ onData: (packet) => {
172
+ if (global.TW_DEBUG) console.log('§90§30§106 CHART SESSION §0 DATA', packet);
173
+
174
+ if (typeof packet.data[1] === 'string' && this.#studyListeners[packet.data[1]]) {
175
+ this.#studyListeners[packet.data[1]](packet);
176
+ return;
177
+ }
178
+
179
+ if (packet.type === 'symbol_resolved') {
180
+ this.#infos = {
181
+ series_id: packet.data[1],
182
+ ...packet.data[2],
183
+ };
184
+
185
+ this.#handleEvent('symbolLoaded');
186
+ return;
187
+ }
188
+
189
+ if (['timescale_update', 'du'].includes(packet.type)) {
190
+ Object.keys(packet.data[1]).forEach((k) => {
191
+ if (k === '$prices') {
192
+ const periods = packet.data[1].$prices;
193
+ if (!periods || !periods.s) return;
194
+
195
+ periods.s.forEach((p) => {
196
+ [this.#chartSession.indexes[p.i]] = p.v;
197
+ this.#periods[p.v[0]] = {
198
+ time: p.v[0],
199
+ open: p.v[1],
200
+ close: p.v[4],
201
+ max: p.v[2],
202
+ min: p.v[3],
203
+ change: Math.round(p.v[5] * 100) / 100,
204
+ };
205
+ });
206
+
207
+ return;
208
+ }
209
+
210
+ if (this.#studyListeners[k]) this.#studyListeners[k](packet);
211
+ });
212
+
213
+ this.#handleEvent('update');
214
+ return;
215
+ }
216
+
217
+ if (packet.type === 'symbol_error') {
218
+ this.#handleError(`(${packet.data[1]}) Symbol error:`, packet.data[2]);
219
+ return;
220
+ }
221
+
222
+ if (packet.type === 'series_error') {
223
+ this.#handleError('Series error:', packet.data[3]);
224
+ return;
225
+ }
226
+
227
+ if (packet.type === 'critical_error') {
228
+ const [, name, description] = packet.data;
229
+ this.#handleError('Critical error:', name, description);
230
+ }
231
+ },
232
+ };
233
+
234
+ this.#client.send('chart_create_session', [this.#sessionID]);
235
+ }
236
+
237
+ #seriesCreated = false;
238
+
239
+ #currentSeries = 0;
240
+
241
+ /**
242
+ * @param {import('../types').TimeFrame} timeframe Chart period timeframe
243
+ * @param {number} [range] Number of loaded periods/candles (Default: 100)
244
+ */
245
+ setSeries(timeframe = '240', range = 100) {
246
+ if (!this.#currentSeries) {
247
+ this.#handleError('Please set the market before setting series');
248
+ return;
249
+ }
250
+
251
+ this.#periods = {};
252
+
253
+ this.#client.send(`${this.#seriesCreated ? 'modify' : 'create'}_series`, [
254
+ this.#sessionID,
255
+ '$prices',
256
+ 's1',
257
+ `ser_${this.#currentSeries}`,
258
+ timeframe,
259
+ this.#seriesCreated ? '' : range,
260
+ ]);
261
+
262
+ this.#seriesCreated = true;
263
+ }
264
+
265
+ /**
266
+ * Set the chart market
267
+ * @param {string} symbol Market symbol
268
+ * @param {Object} [options] Chart options
269
+ * @param {import('../types').TimeFrame} [options.timeframe] Chart period timeframe
270
+ * @param {number} [options.range] Number of loaded periods/candles (Default: 100)
271
+ * @param {'splits' | 'dividends'} [options.adjustment] Market adjustment
272
+ * @param {'regular' | 'extended'} [options.session] Chart session
273
+ * @param {'EUR' | 'USD' | string} [options.currency] Chart currency
274
+ * @param {ChartType} [options.type] Chart custom type
275
+ * @param {ChartInputs} [options.inputs] Chart custom inputs
276
+ */
277
+ setMarket(symbol, options = {}) {
278
+ this.#periods = {};
279
+
280
+ const symbolInit = {
281
+ symbol: symbol || 'BTCEUR',
282
+ adjustment: options.adjustment || 'splits',
283
+ };
284
+
285
+ if (options.session) symbolInit.session = options.session;
286
+
287
+ if (options.currency) symbolInit['currency-id'] = options.currency;
288
+
289
+ const chartInit = (options.type && ChartTypes[options.type]) ? {} : symbolInit;
290
+
291
+ if (options.type && ChartTypes[options.type]) {
292
+ chartInit.symbol = symbolInit;
293
+ chartInit.type = ChartTypes[options.type];
294
+ chartInit.inputs = { ...options.inputs };
295
+ }
296
+
297
+ this.#currentSeries += 1;
298
+
299
+ this.#client.send('resolve_symbol', [
300
+ this.#sessionID,
301
+ `ser_${this.#currentSeries}`,
302
+ `=${JSON.stringify(chartInit)}`,
303
+ ]);
304
+
305
+ this.setSeries(options.timeframe, options.range);
306
+ }
307
+
308
+ /**
309
+ * Set the chart timezone
310
+ * @param {import('../types').Timezone} timezone New timezone
311
+ */
312
+ setTimezone(timezone) {
313
+ this.#periods = {};
314
+ this.#client.send('switch_timezone', [this.#sessionID, timezone]);
315
+ }
316
+
317
+ /**
318
+ * Fetch x additional previous periods/candles values
319
+ * @param {number} number Number of additional periods/candles you want to fetch
320
+ */
321
+ fetchMore(number = 1) {
322
+ this.#client.send('request_more_data', [this.#sessionID, '$prices', number]);
323
+ }
324
+
325
+ /**
326
+ * When a symbol is loaded
327
+ * @param {() => void} cb
328
+ * @event
329
+ */
330
+ onSymbolLoaded(cb) {
331
+ this.#callbacks.symbolLoaded.push(cb);
332
+ }
333
+
334
+ /**
335
+ * When a chart update happens
336
+ * @param {() => void} cb
337
+ * @event
338
+ */
339
+ onUpdate(cb) {
340
+ this.#callbacks.update.push(cb);
341
+ }
342
+
343
+ /**
344
+ * When chart error happens
345
+ * @param {(...any) => void} cb Callback
346
+ * @event
347
+ */
348
+ onError(cb) {
349
+ this.#callbacks.error.push(cb);
350
+ }
351
+
352
+ /** @type {ChartSessionBridge} */
353
+ #chartSession = {
354
+ sessionID: this.#sessionID,
355
+ studyListeners: this.#studyListeners,
356
+ indexes: {},
357
+ send: (t, p) => this.#client.send(t, p),
358
+ };
359
+
360
+ Study = studyConstructor(this.#chartSession);
361
+
362
+ /** Delete the chart session */
363
+ delete() {
364
+ this.#client.send('quote_delete_session', [this.#sessionID]);
365
+ delete this.#client.sessions[this.#sessionID];
366
+ }
367
+ };