@mathieuc/tradingview 2.0.7 → 3.0.0

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,308 @@
1
+ const TRANSLATOR = {
2
+ /** @typedef {'right' | 'left' | 'both' | 'none'} ExtendValue */
3
+ extend: {
4
+ r: 'right',
5
+ l: 'left',
6
+ b: 'both',
7
+ n: 'none',
8
+ },
9
+
10
+ /** @typedef {'price' | 'abovebar' | 'belowbar'} yLocValue */
11
+ yLoc: {
12
+ pr: 'price',
13
+ ab: 'abovebar',
14
+ bl: 'belowbar',
15
+ },
16
+
17
+ /**
18
+ * @typedef {'none' | 'xcross' | 'cross' | 'triangleup'
19
+ * | 'triangledown' | 'flag' | 'circle' | 'arrowup'
20
+ * | 'arrowdown' | 'label_up' | 'label_down' | 'label_left'
21
+ * | 'label_right' | 'label_lower_left' | 'label_lower_right'
22
+ * | 'label_upper_left' | 'label_upper_right' | 'label_center'
23
+ * | 'square' | 'diamond'
24
+ * } LabelStyleValue
25
+ * */
26
+ labelStyle: {
27
+ n: 'none',
28
+ xcr: 'xcross',
29
+ cr: 'cross',
30
+ tup: 'triangleup',
31
+ tdn: 'triangledown',
32
+ flg: 'flag',
33
+ cir: 'circle',
34
+ aup: 'arrowup',
35
+ adn: 'arrowdown',
36
+ lup: 'label_up',
37
+ ldn: 'label_down',
38
+ llf: 'label_left',
39
+ lrg: 'label_right',
40
+ llwlf: 'label_lower_left',
41
+ llwrg: 'label_lower_right',
42
+ luplf: 'label_upper_left',
43
+ luprg: 'label_upper_right',
44
+ lcn: 'label_center',
45
+ sq: 'square',
46
+ dia: 'diamond',
47
+ },
48
+
49
+ /**
50
+ * @typedef {'solid' | 'dotted' | 'dashed'| 'arrow_left'
51
+ * | 'arrow_right' | 'arrow_both'} LineStyleValue
52
+ */
53
+ lineStyle: {
54
+ sol: 'solid',
55
+ dot: 'dotted',
56
+ dsh: 'dashed',
57
+ al: 'arrow_left',
58
+ ar: 'arrow_right',
59
+ ab: 'arrow_both',
60
+ },
61
+
62
+ /** @typedef {'solid' | 'dotted' | 'dashed'} BoxStyleValue */
63
+ boxStyle: {
64
+ sol: 'solid',
65
+ dot: 'dotted',
66
+ dsh: 'dashed',
67
+ },
68
+ };
69
+
70
+ /**
71
+ * @typedef {'auto' | 'huge' | 'large'
72
+ * | 'normal' | 'small' | 'tiny'} SizeValue
73
+ */
74
+ /** @typedef {'top' | 'center' | 'bottom'} VAlignValue */
75
+ /** @typedef {'left' | 'center' | 'right'} HAlignValue */
76
+ /** @typedef {'none' | 'auto'} TextWrapValue */
77
+ /**
78
+ * @typedef {'top_left' | 'top_center' | 'top_right'
79
+ * | 'middle_left' | 'middle_center' | 'middle_right'
80
+ * | 'bottom_left' | 'bottom_center' | 'bottom_right'
81
+ * } TablePositionValue
82
+ */
83
+
84
+ /**
85
+ * @typedef {Object} GraphicLabel
86
+ * @prop {number} id Drawing ID
87
+ * @prop {number} x Label x position
88
+ * @prop {number} y Label y position
89
+ * @prop {yLocValue} yLoc yLoc mode
90
+ * @prop {string} text Label text
91
+ * @prop {LabelStyleValue} style Label style
92
+ * @prop {number} color
93
+ * @prop {number} textColor
94
+ * @prop {SizeValue} size Label size
95
+ * @prop {HAlignValue} textAlign Text horizontal align
96
+ * @prop {string} toolTip Tooltip text
97
+ */
98
+
99
+ /**
100
+ * @typedef {Object} GraphicLine
101
+ * @prop {number} id Drawing ID
102
+ * @prop {number} x1 First x position
103
+ * @prop {number} y1 First y position
104
+ * @prop {number} x2 Second x position
105
+ * @prop {number} y2 Second y position
106
+ * @prop {ExtendValue} extend Horizontal extend
107
+ * @prop {LineStyleValue} style Line style
108
+ * @prop {number} color Line color
109
+ * @prop {number} width Line width
110
+ */
111
+
112
+ /**
113
+ * @typedef {Object} GraphicBox
114
+ * @prop {number} id Drawing ID
115
+ * @prop {number} x1 First x position
116
+ * @prop {number} y1 First y position
117
+ * @prop {number} x2 Second x position
118
+ * @prop {number} y2 Second y position
119
+ * @prop {number} color Box color
120
+ * @prop {number} bgColor Background color
121
+ * @prop {ExtendValue} extend Horizontal extend
122
+ * @prop {BoxStyleValue} style Box style
123
+ * @prop {number} width Box width
124
+ * @prop {string} text Text
125
+ * @prop {SizeValue} textSize Text size
126
+ * @prop {number} textColor Text color
127
+ * @prop {VAlignValue} textVAlign Text vertical align
128
+ * @prop {HAlignValue} textHAlign Text horizontal align
129
+ * @prop {TextWrapValue} textWrap Text wrap
130
+ */
131
+
132
+ /**
133
+ * @typedef {Object} TableCell
134
+ * @prop {number} id Drawing ID
135
+ * @prop {string} text Cell text
136
+ * @prop {number} width Cell width
137
+ * @prop {number} height Cell height
138
+ * @prop {number} textColor Text color
139
+ * @prop {HAlignValue} textHAlign Text horizontal align
140
+ * @prop {VAlignValue} textVAlign Text Vertical align
141
+ * @prop {SizeValue} textSize Text size
142
+ * @prop {number} bgColor Background color
143
+ */
144
+
145
+ /**
146
+ * @typedef {Object} GraphicTable
147
+ * @prop {number} id Drawing ID
148
+ * @prop {TablePositionValue} position Table position
149
+ * @prop {number} rows Number of rows
150
+ * @prop {number} columns Number of columns
151
+ * @prop {number} bgColor Background color
152
+ * @prop {number} frameColor Frame color
153
+ * @prop {number} frameWidth Frame width
154
+ * @prop {number} borderColor Border color
155
+ * @prop {number} borderWidth Border width
156
+ * @prop {() => TableCell[][]} cells Table cells matrix
157
+ */
158
+
159
+ /**
160
+ * @typedef {Object} GraphicHorizline
161
+ * @prop {number} id Drawing ID
162
+ * @prop {number} level Y position of the line
163
+ * @prop {number} startIndex Start index of the line (`chart.periods[line.startIndex]`)
164
+ * @prop {number} endIndex End index of the line (`chart.periods[line.endIndex]`)
165
+ * @prop {boolean} extendRight Is the line extended to the right
166
+ * @prop {boolean} extendLeft Is the line extended to the left
167
+ */
168
+
169
+ /**
170
+ * @typedef {Object} GraphicPoint
171
+ * @prop {number} index X position of the point
172
+ * @prop {number} level Y position of the point
173
+ */
174
+
175
+ /**
176
+ * @typedef {Object} GraphicPolygon
177
+ * @prop {number} id Drawing ID
178
+ * @prop {GraphicPoint[]} points List of polygon points
179
+ */
180
+
181
+ /**
182
+ * @typedef {Object} GraphicHorizHist
183
+ * @prop {number} id Drawing ID
184
+ * @prop {number} priceLow Low Y position
185
+ * @prop {number} priceHigh High Y position
186
+ * @prop {number} firstBarTime First X position
187
+ * @prop {number} lastBarTime Last X position
188
+ * @prop {number[]} rate List of values
189
+ */
190
+
191
+ /**
192
+ * @typedef {Object} GraphicData List of drawings indexed by type
193
+ * @prop {GraphicLabel[]} labels List of labels drawings
194
+ * @prop {GraphicLine[]} lines List of lines drawings
195
+ * @prop {GraphicBox[]} boxes List of boxes drawings
196
+ * @prop {GraphicTable[]} tables List of tables drawings
197
+ * @prop {GraphicPolygon[]} polygons List of polygons drawings
198
+ * @prop {GraphicHorizHist[]} horizHists List of horizontal histograms drawings
199
+ * @prop {GraphicHorizline[]} horizLines List of horizontal lines drawings
200
+ */
201
+
202
+ /**
203
+ * @param {Object} rawGraphic Raw graphic data
204
+ * @param {Object} indexes Drawings xPos indexes
205
+ * @returns {GraphicData}
206
+ */
207
+ module.exports = function graphicParse(rawGraphic = {}, indexes = []) {
208
+ // console.log('indexes', indexes);
209
+ return {
210
+ labels: Object.values(rawGraphic.dwglabels ?? {}).map((l) => ({
211
+ id: l.id,
212
+ x: indexes[l.x],
213
+ y: l.y,
214
+ yLoc: TRANSLATOR.yLoc[l.yl] ?? l.yl,
215
+ text: l.t,
216
+ style: TRANSLATOR.labelStyle[l.st] ?? l.st,
217
+ color: l.ci,
218
+ textColor: l.tci,
219
+ size: l.sz,
220
+ textAlign: l.ta,
221
+ toolTip: l.tt,
222
+ })),
223
+
224
+ lines: Object.values(rawGraphic.dwglines ?? {}).map((l) => ({
225
+ id: l.id,
226
+ x1: indexes[l.x1],
227
+ y1: l.y1,
228
+ x2: indexes[l.x2],
229
+ y2: l.y2,
230
+ extend: TRANSLATOR.extend[l.ex] ?? l.ex,
231
+ style: TRANSLATOR.lineStyle[l.st] ?? l.st,
232
+ color: l.ci,
233
+ width: l.w,
234
+ })),
235
+
236
+ boxes: Object.values(rawGraphic.dwgboxes ?? {}).map((b) => ({
237
+ id: b.id,
238
+ x1: indexes[b.x1],
239
+ y1: b.y1,
240
+ x2: indexes[b.x2],
241
+ y2: b.y2,
242
+ color: b.c,
243
+ bgColor: b.bc,
244
+ extend: TRANSLATOR.extend[b.ex] ?? b.ex,
245
+ style: TRANSLATOR.boxStyle[b.st] ?? b.st,
246
+ width: b.w,
247
+ text: b.t,
248
+ textSize: b.ts,
249
+ textColor: b.tc,
250
+ textVAlign: b.tva,
251
+ textHAlign: b.tha,
252
+ textWrap: b.tw,
253
+ })),
254
+
255
+ tables: Object.values(rawGraphic.dwgtables ?? {}).map((t) => ({
256
+ id: t.id,
257
+ position: t.pos,
258
+ rows: t.rows,
259
+ columns: t.cols,
260
+ bgColor: t.bgc,
261
+ frameColor: t.frmc,
262
+ frameWidth: t.frmw,
263
+ borderColor: t.brdc,
264
+ borderWidth: t.brdw,
265
+ cells: () => {
266
+ const matrix = [];
267
+ Object.values(rawGraphic.dwgtablecells ?? {}).forEach((cell) => {
268
+ if (cell.tid !== t.id) return;
269
+ if (!matrix[cell.row]) matrix[cell.row] = [];
270
+ matrix[cell.row][cell.col] = {
271
+ id: cell.id,
272
+ text: cell.t,
273
+ width: cell.w,
274
+ height: cell.h,
275
+ textColor: cell.tc,
276
+ textHAlign: cell.tha,
277
+ textVAlign: cell.tva,
278
+ textSize: cell.ts,
279
+ bgColor: cell.bgc,
280
+ };
281
+ });
282
+ return matrix;
283
+ },
284
+ })),
285
+
286
+ horizLines: Object.values(rawGraphic.horizlines ?? {}).map((h) => ({
287
+ ...h,
288
+ startIndex: indexes[h.startIndex],
289
+ endIndex: indexes[h.endIndex],
290
+ })),
291
+
292
+ polygons: Object.values(rawGraphic.polygons ?? {}).map((p) => ({
293
+ ...p,
294
+ points: p.points.map((pt) => ({
295
+ ...pt,
296
+ index: indexes[pt.index],
297
+ })),
298
+ })),
299
+
300
+ horizHists: Object.values(rawGraphic.hhists ?? {}).map((h) => ({
301
+ ...h,
302
+ firstBarTime: indexes[h.firstBarTime],
303
+ lastBarTime: indexes[h.lastBarTime],
304
+ })),
305
+
306
+ raw: () => rawGraphic,
307
+ };
308
+ };
@@ -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
+ };