@mathieuc/tradingview 2.0.91 → 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,41 @@
1
+ const TradingView = require('../main');
2
+
3
+ if (!process.argv[2]) throw Error('Please specify your \'sessionid\' cookie');
4
+
5
+ const client = new TradingView.Client({
6
+ token: process.argv[2],
7
+ });
8
+
9
+ const chart = new client.Session.Chart();
10
+ chart.setMarket('NASDAQ:AAPL', {
11
+ type: 'Renko',
12
+ timeframe: '1',
13
+ inputs: {
14
+ source: 'close',
15
+ sources: 'Close',
16
+ boxSize: 3,
17
+ style: 'Traditional',
18
+ atrLength: 14,
19
+ wicks: true,
20
+ },
21
+ });
22
+
23
+ TradingView.searchIndicator('CM_Ultimate_MA_MTF_V2').then(async (indicator) => {
24
+ console.log(
25
+ '*** Indicator information\n',
26
+ indicator[0].id,
27
+ );
28
+ const pineIndicator = await indicator[0].get();
29
+ const study = new chart.Study(pineIndicator);
30
+ study.onUpdate(() => {
31
+ console.log(
32
+ '\n*** Chart time and close:\n',
33
+ chart.periods[0].time,
34
+ chart.periods[0].close,
35
+ );
36
+ console.log(
37
+ '\n*** Indicator values:\n',
38
+ study.periods[0].MultiTimeframe_Moving_Avg,
39
+ );
40
+ });
41
+ });
package/main.js CHANGED
@@ -1,504 +1,9 @@
1
- const WebSocket = require('ws');
2
- const JSZip = require('jszip');
3
-
4
- const {
5
- search, getScreener, getTA, getIndicator,
6
- getUser, getChartToken, getDrawings, searchIndicator,
7
- } = require('./miscRequests');
8
-
9
- let onPacket = () => null;
10
-
11
- function parse(str) {
12
- const packets = str.replace(/~h~/g, '').split(/~m~[0-9]{1,}~m~/g).map((p) => {
13
- if (!p) return false;
14
- try {
15
- return JSON.parse(p);
16
- } catch (error) {
17
- console.warn('Cant parse', p);
18
- return false;
19
- }
20
- }).filter((p) => p);
21
-
22
- packets.forEach((packet) => {
23
- if (packet.m === 'protocol_error') {
24
- return onPacket({
25
- type: 'error',
26
- syntax: packet.p[0],
27
- });
28
- }
29
-
30
- if (packet.m && packet.p) {
31
- return onPacket({
32
- type: packet.m,
33
- session: packet.p[0],
34
- data: packet.p[1],
35
- });
36
- }
37
-
38
- if (typeof packet === 'number') return onPacket({ type: 'ping', ping: packet });
39
-
40
- return onPacket({ type: 'info', ...packet });
41
- });
42
- }
43
-
44
- function genSession() {
45
- let r = '';
46
- const c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
47
- for (let i = 0; i < 12; i += 1) r += c.charAt(Math.floor(Math.random() * c.length));
48
- return `qs_${r}`;
49
- }
50
-
51
- module.exports = (autoInit = true) => {
52
- const callbacks = {
53
- connected: [],
54
- disconnected: [],
55
- logged: [],
56
- subscribed: [],
57
- ping: [],
58
- price: [],
59
- data: [],
60
-
61
- error: [],
62
- event: [],
63
- };
64
-
65
- const chartEventNames = [
66
- 'du', 'timescale_update',
67
- 'series_loading', 'series_completed', 'series_error',
68
- 'symbol_resolved', 'symbol_error',
69
- 'study_loading', 'study_error',
70
- ];
71
- const chartCBs = {};
72
-
73
- function handleEvent(ev, ...data) {
74
- callbacks[ev].forEach((e) => e(...data));
75
- callbacks.event.forEach((e) => e(ev, ...data));
76
- }
77
-
78
- function handleError(...msgs) {
79
- if (callbacks.error.length === 0) console.error(...msgs);
80
- else handleEvent('error', ...msgs);
81
- }
82
-
83
- const ws = new WebSocket('wss://widgetdata.tradingview.com/socket.io/websocket', {
84
- origin: 'https://s.tradingview.com',
85
- });
86
-
87
- let logged = false;
88
- /** ID of the quote session */
89
- let sessionId = '';
90
-
91
- /** List of subscribed symbols */
92
- let subscribed = [];
93
-
94
- /** Websockets status */
95
- let isEnded = false;
96
-
97
- /**
98
- * Send a custom packet
99
- * @param {string} t Packet type
100
- * @param {string[]} p Packet data
101
- * @example
102
- * // Subscribe manualy to BTCEUR
103
- * send('quote_add_symbols', [sessionId, 'BTCEUR']);
104
- */
105
- function send(t, p = []) {
106
- if (!sessionId) return;
107
-
108
- const msg = JSON.stringify({ m: t, p });
109
- ws.send(`~m~${msg.length}~m~${msg}`);
110
- }
111
-
112
- ws.on('open', () => {
113
- sessionId = genSession();
114
- handleEvent('connected');
115
- });
116
-
117
- ws.on('close', () => {
118
- logged = false;
119
- sessionId = '';
120
- handleEvent('disconnected');
121
- });
122
-
123
- ws.on('message', parse);
124
-
125
- onPacket = (packet) => {
126
- if (packet.type === 'ping') {
127
- const pingStr = `~h~${packet.ping}`;
128
- ws.send(`~m~${pingStr.length}~m~${pingStr}`);
129
- handleEvent('ping', packet.ping);
130
- return;
131
- }
132
-
133
- if (packet.type === 'quote_completed' && packet.data) {
134
- handleEvent('subscribed', packet.data);
135
- return;
136
- }
137
-
138
- if (packet.type === 'qsd' && packet.data.n && packet.data.v.lp) {
139
- handleEvent('price', {
140
- symbol: packet.data.n,
141
- price: packet.data.v.lp,
142
- });
143
-
144
- return;
145
- }
146
-
147
- if (chartEventNames.includes(packet.type) && chartCBs[packet.session]) {
148
- chartCBs[packet.session](packet);
149
- return;
150
- }
151
-
152
- if (!logged && packet.type === 'info') {
153
- if (autoInit) {
154
- send('set_auth_token', ['unauthorized_user_token']);
155
- send('quote_create_session', [sessionId]);
156
- send('quote_set_fields', [sessionId, 'lp']);
157
-
158
- subscribed.forEach((symbol) => send('quote_add_symbols', [sessionId, symbol]));
159
- }
160
-
161
- handleEvent('logged', packet);
162
- return;
163
- }
164
-
165
- if (packet.type === 'error') {
166
- handleError(`Market API critical error: ${packet.syntax}`);
167
- ws.close();
168
- return;
169
- }
170
-
171
- handleEvent('data', packet);
172
- };
173
-
174
- return {
175
- /** Event listener
176
- * @param { 'connected' | 'disconnected' | 'logged'
177
- * | 'subscribed' | 'price' | 'data' | 'error' | 'ping' } event Event
178
- * @param {(...data: object) => null} cb Callback
179
- */
180
- on(event, cb) {
181
- if (!callbacks[event]) {
182
- console.log('Wrong event:', event);
183
- console.log('Available events:', Object.keys(callbacks));
184
- return;
185
- }
186
-
187
- callbacks[event].push(cb);
188
- },
189
-
190
- /**
191
- * Close the websocket connection
192
- * @return {Promise<void>} When websocket is closed
193
- */
194
- end() {
195
- return new Promise((cb) => {
196
- isEnded = true;
197
- ws.close();
198
- cb();
199
- });
200
- },
201
-
202
- search,
203
- getScreener,
204
- getTA,
205
- subscribed,
206
- searchIndicator,
207
- getUser,
208
- getChartToken,
209
- getDrawings,
210
-
211
- /**
212
- * Unsubscribe to a market
213
- * @param {string} symbol Market symbol (Example: BTCEUR or COINBASE:BTCEUR)
214
- */
215
- subscribe(symbol) {
216
- if (subscribed.includes(symbol)) return;
217
- send('quote_add_symbols', [sessionId, symbol]);
218
- subscribed.push(symbol);
219
- },
220
-
221
- /**
222
- * Unsubscribe from a market
223
- * @param {string} symbol Market symbol (Example: BTCEUR or COINBASE:BTCEUR)
224
- */
225
- unsubscribe(symbol) {
226
- if (!subscribed.includes(symbol)) return;
227
- send('quote_remove_symbols', [sessionId, symbol]);
228
- subscribed = subscribed.filter((s) => s !== symbol);
229
- },
230
-
231
- /**
232
- * @typedef {Object} IndicatorInfos Indicator infos
233
- * @property {string} id ID of the indicator (Like: XXX;XXXXXXXXXXXXXXXXXXXXX)
234
- * @property {string} [name] Name of the indicator
235
- * @property {string} version Wanted version of the indicator
236
- * @property {(string | number | boolean | null)[]} [settings] Indicator settings value
237
- * @property {'study' | 'strategy'} [type] Script type
238
- *
239
- * @typedef {Object} ChartInfos
240
- * @property {string} [session] User 'sessionid' cookie
241
- * @property {string} symbol Market symbol (Example: BTCEUR or COINBASE:BTCEUR)
242
- * @property { '1' | '3' | '5' | '15' | '30' | '45'
243
- * | '60' | '120' | '180' | '240'
244
- * | '1D' | '1W' | '1M'
245
- * } [period] Period
246
- * @property {number} [range] Number of loaded periods
247
- * @property {string} [timezone] Timezone in 'Europe/Paris' format
248
- * @property {IndicatorInfos[]} [indicators] List of indicators
249
- */
250
-
251
- /**
252
- * @typedef {Object} Period List of prices / indicator values
253
- * @property {number} $time
254
- * @property {{
255
- * time: number, open: number, close: number,
256
- * max: number, min: number, change: number,
257
- * }} $prices
258
- */
259
-
260
- /**
261
- * Init a chart instance
262
- * @param {ChartInfos} chart
263
- * @param {{(prices: Period[], strategies: Object<string, StrategyReport>): null}} onUpdate
264
- */
265
- async initChart(chart, onUpdate) {
266
- const chartSession = genSession();
267
- const periods = [];
268
-
269
- /**
270
- * @typedef {Object} RelAbsValue Relative and Absolute values
271
- * @property {number} v Absolute value
272
- * @property {number} p Relative value
273
- */
274
-
275
- /**
276
- * @typedef {Object} TradeReport Trade report
277
-
278
- * @property {Object} entry Trade entry
279
- * @property {string} entry.name Trade name
280
- * @property {'long' | 'short'} entry.type Entry type (long/short)
281
- * @property {number} entry.value Entry price value
282
- * @property {number} entry.time Entry timestamp
283
-
284
- * @property {Object} exit Trade exit
285
- * @property {'' | string} exit.name Trade name ('' if false exit)
286
- * @property {number} exit.value Exit price value
287
- * @property {number} exit.time Exit timestamp
288
-
289
- * @property {number} quantity Trade quantity
290
- * @property {RelAbsValue} profit Trade profit
291
- * @property {RelAbsValue} cumulative Trade cummulative profit
292
- * @property {RelAbsValue} runup Trade run-up
293
- * @property {RelAbsValue} drawdown Trade drawdown
294
- */
295
-
296
- /**
297
- * @typedef {Object} PerfReport
298
- * @property {number} avgBarsInTrade Average bars in trade
299
- * @property {number} avgBarsInWinTrade Average bars in winning trade
300
- * @property {number} avgBarsInLossTrade Average bars in losing trade
301
- * @property {number} avgTrade Average trade gain
302
- * @property {number} avgTradePercent Average trade performace
303
- * @property {number} avgLosTrade Average losing trade gain
304
- * @property {number} avgLosTradePercent Average losing trade performace
305
- * @property {number} avgWinTrade Average winning trade gain
306
- * @property {number} avgWinTradePercent Average winning trade performace
307
- * @property {number} commissionPaid Commission paid
308
- * @property {number} grossLoss Gross loss value
309
- * @property {number} grossLossPercent Gross loss percent
310
- * @property {number} grossProfit Gross profit
311
- * @property {number} grossProfitPercent Gross profit percent
312
- * @property {number} largestLosTrade Largest losing trade gain
313
- * @property {number} largestLosTradePercent Largent losing trade performance
314
- * @property {number} largestWinTrade Largest winning trade gain
315
- * @property {number} largestWinTradePercent Largest winning trade performance
316
- * @property {number} marginCalls Margin calls
317
- * @property {number} maxContractsHeld Max Contracts Held
318
- * @property {number} netProfit Net profit
319
- * @property {number} netProfitPercent Net performance
320
- * @property {number} numberOfLosingTrades Number of losing trades
321
- * @property {number} numberOfWiningTrades Number of winning trades
322
- * @property {number} percentProfitable Strategy winrate
323
- * @property {number} profitFactor Profit factor
324
- * @property {number} ratioAvgWinAvgLoss Ratio Average Win / Average Loss
325
- * @property {number} totalOpenTrades Total open trades
326
- * @property {number} totalTrades Total trades
327
- */
328
-
329
- /**
330
- * @typedef {Object} StrategyReport
331
- * @property {'EUR' | 'USD' | 'JPY' | '' | 'CHF'} currency Selected currency
332
- * @property {TradeReport[]} trades Trade list
333
- * @property {Object} history History Chart value
334
- * @property {number[]} history.buyHold Buy hold values
335
- * @property {number[]} history.buyHoldPercent Buy hold percent values
336
- * @property {number[]} history.drawDown Drawdown values
337
- * @property {number[]} history.drawDownPercent Drawdown percent values
338
- * @property {number[]} history.equity Equity values
339
- * @property {number[]} history.equityPercent Equity percent values
340
- * @property {Object} performance Strategy performance
341
- * @property {PerfReport} performance.all Strategy long/short performances
342
- * @property {PerfReport} performance.long Strategy long performances
343
- * @property {PerfReport} performance.short Strategy short performances
344
- * @property {number} performance.buyHoldReturn Strategy Buy & Hold Return
345
- * @property {number} performance.buyHoldReturnPercent Strategy Buy & Hold Return percent
346
- * @property {number} performance.maxDrawDown Strategy max drawdown
347
- * @property {number} performance.maxDrawDownPercent Strategy max drawdown percent
348
- * @property {number} performance.openPL Strategy Open P&L (Profit And Loss)
349
- * @property {number} performance.openPLPercent Strategy Open P&L (Profit And Loss) percent
350
- * @property {number} performance.sharpeRatio Strategy Sharpe Ratio
351
- * @property {number} performance.sortinoRatio Strategy Sortino Ratio
352
- */
353
-
354
- /** @type {Object<string, StrategyReport>} Strategies */
355
- const strategies = {};
356
-
357
- const indicators = await Promise.all(
358
- (chart.indicators || []).map((i) => getIndicator(i.id, i.version, i.settings, i.type)),
359
- );
360
-
361
- async function updatePeriods(packet) {
362
- const newData = packet.data;
363
-
364
- await Promise.all(Object.keys(newData).map(async (type) => {
365
- const std = chart.indicators[parseInt(type, 10)] || {};
366
-
367
- if (newData[type].ns && newData[type].ns.d) {
368
- const stratData = JSON.parse(newData[type].ns.d);
369
-
370
- if (stratData.dataCompressed) {
371
- const zip = new JSZip();
372
- const data = JSON.parse(
373
- await (
374
- await zip.loadAsync(stratData.dataCompressed, { base64: true })
375
- ).file('').async('text'),
376
- );
377
-
378
- strategies[std.name || type] = {
379
- currency: data.report.currency,
380
-
381
- trades: data.report.trades.map((t) => ({
382
- entry: {
383
- name: t.e.c,
384
- type: (t.e.tp[0] === 's' ? 'short' : 'long'),
385
- value: t.e.p,
386
- time: t.e.tm,
387
- },
388
- exit: {
389
- name: t.x.c,
390
- value: t.x.p,
391
- time: t.x.tm,
392
- },
393
- quantity: t.q,
394
- profit: t.tp,
395
- cumulative: t.cp,
396
- runup: t.rn,
397
- drawdown: t.dd,
398
- })),
399
-
400
- history: {
401
- buyHold: data.report.buyHold,
402
- buyHoldPercent: data.report.buyHoldPercent,
403
- drawDown: data.report.drawDown,
404
- drawDownPercent: data.report.drawDownPercent,
405
- equity: data.report.equity,
406
- equityPercent: data.report.equityPercent,
407
- },
408
-
409
- performance: data.report.performance,
410
- };
411
- return;
412
- }
413
-
414
- if (stratData.data && stratData.data.report && stratData.data.report.performance) {
415
- if (!strategies[std.name || type]) strategies[std.name || type] = { performance: {} };
416
- strategies[std.name || type].performance = stratData.data.report.performance;
417
- return;
418
- }
419
-
420
- return;
421
- }
422
-
423
- (newData[type].s || newData[type].st || []).forEach((p) => {
424
- if (!periods[p.i]) periods[p.i] = {};
425
-
426
- if (newData[type].s) {
427
- [periods[p.i].$time] = p.v;
428
-
429
- periods[p.i][type] = {
430
- open: p.v[1],
431
- close: p.v[4],
432
- max: p.v[2],
433
- min: p.v[3],
434
- change: Math.round(p.v[5] * 100) / 100,
435
- };
436
- }
437
-
438
- if (newData[type].st) {
439
- const period = {};
440
- const indicator = indicators[parseInt(type, 10)];
441
-
442
- p.v.forEach((val, i) => {
443
- if (i === 0) return;
444
- if (indicator.plots[`plot_${i - 1}`]) period[indicator.plots[`plot_${i - 1}`]] = val;
445
- else period[`_plot_${i - 1}`] = val;
446
- });
447
- periods[p.i][chart.indicators[parseInt(type, 10)].name || `st${type}`] = period;
448
- }
449
- });
450
- }));
451
- }
452
-
453
- chartCBs[chartSession] = async (packet) => {
454
- if (isEnded) return;
455
-
456
- if (['timescale_update', 'du'].includes(packet.type)) {
457
- await updatePeriods(packet);
458
- onUpdate([...periods].reverse(), strategies);
459
- return;
460
- }
461
-
462
- if (packet.type.endsWith('_error')) {
463
- handleError(`Error on '${chart.symbol}' (${chartSession}) chart: "${packet.type}":`, packet);
464
- }
465
- };
466
-
467
- if (chart.session) send('set_auth_token', [(await getUser(chart.session)).authToken]);
468
- send('chart_create_session', [chartSession, '']);
469
- if (chart.timezone) send('switch_timezone', [chartSession, chart.timezone]);
470
- send('resolve_symbol', [chartSession, 'sds_sym_1', `={"symbol":"${chart.symbol || 'BTCEUR'}","adjustment":"splits"}`]);
471
- send('create_series', [chartSession, '$prices', 's1', 'sds_sym_1', (chart.period || '240'), (chart.range || 100), '']);
472
-
473
- indicators.forEach(async (indicator, i) => {
474
- const pineInfos = {
475
- pineId: indicator.pineId,
476
- pineVersion: indicator.pineVersion,
477
- text: indicator.script,
478
- };
479
-
480
- Object.keys(indicator.inputs).forEach((inputID, inp) => {
481
- const input = indicator.inputs[inputID];
482
- if (input.type === 'bool' && typeof input.value !== 'boolean') handleError(`Input '${input.name}' (${inp}) must be a boolean !`);
483
- if (input.type === 'integer' && typeof input.value !== 'number') handleError(`Input '${input.name}' (${inp}) must be a number !`);
484
- if (input.type === 'float' && typeof input.value !== 'number') handleError(`Input '${input.name}' (${inp}) must be a number !`);
485
- if (input.type === 'text' && typeof input.value !== 'string') handleError(`Input '${input.name}' (${inp}) must be a string !`);
486
- if (input.options && !input.options.includes(input.value)) {
487
- handleError(`Input '${input.name}' (${inp}) must be one of these values:`, input.options);
488
- }
489
-
490
- pineInfos[inputID] = {
491
- v: input.value,
492
- f: input.isFake,
493
- t: input.type,
494
- };
495
- });
496
-
497
- send('create_study', [chartSession, `${i}`, 'st1', '$prices', indicator.typeID, pineInfos]);
498
- });
499
- },
500
-
501
- send,
502
- sessionId,
503
- };
504
- };
1
+ const miscRequests = require('./src/miscRequests');
2
+ const Client = require('./src/client');
3
+ const BuiltInIndicator = require('./src/classes/BuiltInIndicator');
4
+ const PineIndicator = require('./src/classes/PineIndicator');
5
+
6
+ module.exports = { ...miscRequests };
7
+ module.exports.Client = Client;
8
+ module.exports.BuiltInIndicator = BuiltInIndicator;
9
+ module.exports.PineIndicator = PineIndicator;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mathieuc/tradingview",
3
- "version": "2.0.91",
3
+ "version": "3.0.0-pre",
4
4
  "description": "Tradingview instant stocks API, indicator alerts, trading bot, and more !",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -32,8 +32,11 @@
32
32
  "ws": "^7.4.3"
33
33
  },
34
34
  "devDependencies": {
35
+ "@babel/eslint-parser": "^7.15.7",
36
+ "@mathieuc/console": "^1.0.1",
35
37
  "eslint": "^7.25.0",
36
38
  "eslint-config-airbnb-base": "^14.2.1",
37
- "eslint-plugin-import": "^2.22.1"
39
+ "eslint-plugin-import": "^2.22.1",
40
+ "jsdoc-to-markdown": "^7.1.0"
38
41
  }
39
42
  }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @typedef {Object} GraphicLabel
3
+ * @prop {number} id Drawing ID
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} GraphicLine
8
+ * @prop {number} id Drawing ID
9
+ */
10
+
11
+ /**
12
+ * @typedef {Object} GraphicBox
13
+ * @prop {number} id Drawing ID
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object} GraphicTable
18
+ * @prop {number} id Drawing ID
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} GraphicHorizline
23
+ * @prop {number} id Drawing ID
24
+ * @prop {number} level Y position of the line
25
+ * @prop {number} startIndex Start index of the line (`chart.periods[line.startIndex]`)
26
+ * @prop {number} endIndex End index of the line (`chart.periods[line.endIndex]`)
27
+ * @prop {boolean} extendRight Is the line extended to the right
28
+ * @prop {boolean} extendLeft Is the line extended to the left
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} GraphicPoint
33
+ * @prop {number} index X position of the point
34
+ * @prop {number} level Y position of the point
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} GraphicPolygon
39
+ * @prop {number} id Drawing ID
40
+ * @prop {GraphicPoint[]} points List of polygon points
41
+ */
42
+
43
+ /**
44
+ * @typedef {Object} GraphicHorizHist
45
+ * @prop {number} id Drawing ID
46
+ * @prop {number} priceLow Low Y position
47
+ * @prop {number} priceHigh High Y position
48
+ * @prop {number} firstBarTime First X position
49
+ * @prop {number} lastBarTime Last X position
50
+ * @prop {number[]} rate List of values
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object} GraphicData List of drawings indexed by type
55
+ * @prop {GraphicLabel[]} labels List of labels drawings
56
+ * @prop {GraphicLine[]} lines List of lines drawings
57
+ * @prop {GraphicBox[]} boxes List of boxes drawings
58
+ * @prop {GraphicTable[]} tables List of tables drawings
59
+ * @prop {GraphicPolygon[]} polygons List of polygons drawings
60
+ * @prop {GraphicHorizHist[]} horizHists List of horizontal histograms drawings
61
+ * @prop {GraphicHorizline[]} horizLines List of horizontal lines drawings
62
+ */
63
+
64
+ /**
65
+ * @param {Object} rawGraphic Raw graphic data
66
+ * @param {Object} indexes Drawings xPos indexes
67
+ * @returns {GraphicData}
68
+ */
69
+ module.exports = function graphicParse(rawGraphic = {}, indexes = []) {
70
+ // console.log('indexes', indexes);
71
+ return {
72
+ labels: Object.values(rawGraphic.dwglabels ?? {}).map((l) => ({
73
+ ...l,
74
+ })),
75
+
76
+ lines: Object.values(rawGraphic.dwglines ?? {}).map((l) => ({
77
+ ...l,
78
+ })),
79
+
80
+ boxes: Object.values(rawGraphic.dwgboxes ?? {}).map((b) => ({
81
+ ...b,
82
+ })),
83
+
84
+ tables: Object.values(rawGraphic.dwgtables ?? {}).map((t) => ({
85
+ ...t,
86
+ })),
87
+
88
+ horizLines: Object.values(rawGraphic.horizlines ?? {}).map((h) => ({
89
+ ...h,
90
+ startIndex: indexes[h.startIndex],
91
+ endIndex: indexes[h.endIndex],
92
+ })),
93
+
94
+ polygons: Object.values(rawGraphic.polygons ?? {}).map((p) => ({
95
+ ...p,
96
+ points: p.points.map((pt) => ({
97
+ ...pt,
98
+ index: indexes[pt.index],
99
+ })),
100
+ })),
101
+
102
+ horizHists: Object.values(rawGraphic.hhists ?? {}).map((h) => ({
103
+ ...h,
104
+ firstBarTime: indexes[h.firstBarTime],
105
+ lastBarTime: indexes[h.lastBarTime],
106
+ })),
107
+
108
+ raw: () => rawGraphic,
109
+ };
110
+ };