@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.
- package/.eslintrc.js +18 -16
- package/.github/FUNDING.yml +1 -0
- package/README.md +26 -96
- package/examples/AllPrivateIndicators.js +35 -0
- package/examples/BuiltInIndicator.js +41 -0
- package/examples/CustomChartType.js +121 -0
- package/examples/CustomTimeframe.js +29 -0
- package/examples/Errors.js +153 -0
- package/examples/GraphicIndicator.js +44 -0
- package/examples/MultipleSyncFetch.js +38 -0
- package/examples/Search.js +15 -0
- package/examples/SimpleChart.js +63 -0
- package/examples/_RenkoIndicator.js +41 -0
- package/main.js +9 -337
- package/package.json +10 -2
- package/src/chart/graphicParser.js +110 -0
- package/src/chart/session.js +367 -0
- package/src/chart/study.js +403 -0
- package/src/classes/BuiltInIndicator.js +119 -0
- package/src/classes/PineIndicator.js +132 -0
- package/src/client.js +280 -0
- package/src/miscRequests.js +525 -0
- package/src/protocol.js +54 -0
- package/src/quote/market.js +123 -0
- package/src/quote/session.js +124 -0
- package/src/types.js +32 -0
- package/src/utils.js +8 -0
- package/test.js +155 -0
- package/tests/alerts/discordWH.js +76 -76
- package/tests/alerts/drawings.js +41 -0
- package/tests/alerts/envLoader.js +11 -11
- package/tests/alerts/manualAlert.js +77 -77
- package/tests/alerts/sentNotifs.js +16 -16
- package/tests/alerts/strategyAlert.js +94 -94
- package/tests/analysis.js +3 -3
- package/tests/indicator.js +42 -42
- package/tests/strategy.js +56 -0
- package/miscRequests.js +0 -285
|
@@ -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
|
+
};
|