@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.
- package/.eslintrc.js +18 -16
- 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 +34 -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 -352
- package/package.json +10 -2
- package/src/chart/graphicParser.js +308 -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 -41
- 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 +13 -13
- package/tests/indicator.js +42 -42
- package/tests/strategy.js +56 -0
- package/miscRequests.js +0 -420
|
@@ -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
|
+
};
|