@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
package/miscRequests.js
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
const https = require('https');
|
|
2
|
-
|
|
3
|
-
const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
|
|
4
|
-
|
|
5
|
-
function fetchScanData(tickers = [], type = '', columns = []) {
|
|
6
|
-
return new Promise((cb, err) => {
|
|
7
|
-
const req = https.request({
|
|
8
|
-
method: 'POST',
|
|
9
|
-
hostname: 'scanner.tradingview.com',
|
|
10
|
-
path: `/${type}/scan`,
|
|
11
|
-
headers: {
|
|
12
|
-
'Content-Type': 'application/json',
|
|
13
|
-
},
|
|
14
|
-
}, (res) => {
|
|
15
|
-
let data = '';
|
|
16
|
-
res.on('data', (c) => { data += c; });
|
|
17
|
-
res.on('end', () => {
|
|
18
|
-
if (!data.startsWith('{')) {
|
|
19
|
-
err(new Error('Wrong screener or symbol'));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
try {
|
|
23
|
-
cb(JSON.parse(data));
|
|
24
|
-
} catch (error) {
|
|
25
|
-
err(new Error('Can\'t parse server response'));
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
req.on('error', err);
|
|
31
|
-
req.end(JSON.stringify({ symbols: { tickers }, columns }));
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @typedef {number} advice
|
|
37
|
-
*
|
|
38
|
-
* @typedef {{ Other: advice, All: advice, MA: advice }} Period
|
|
39
|
-
*
|
|
40
|
-
* @typedef {{
|
|
41
|
-
* '1': Period,
|
|
42
|
-
* '5': Period,
|
|
43
|
-
* '15': Period,
|
|
44
|
-
* '60': Period,
|
|
45
|
-
* '240': Period,
|
|
46
|
-
* '1D': Period,
|
|
47
|
-
* '1W': Period,
|
|
48
|
-
* '1M': Period,
|
|
49
|
-
* }} Periods
|
|
50
|
-
*
|
|
51
|
-
* @typedef {string | 'forex' | 'crypto'
|
|
52
|
-
* | 'america' | 'australia' | 'canada' | 'egypt'
|
|
53
|
-
* | 'germany' | 'india' | 'israel' | 'italy'
|
|
54
|
-
* | 'luxembourg' | 'poland' | 'sweden' | 'turkey'
|
|
55
|
-
* | 'uk' | 'vietnam'} screener
|
|
56
|
-
* You can use `getScreener(exchange)` function for non-forex and non-crypto markets.
|
|
57
|
-
*/
|
|
58
|
-
|
|
59
|
-
module.exports = {
|
|
60
|
-
/**
|
|
61
|
-
* @param {string} exchange Example: BINANCE, EURONEXT, NASDAQ
|
|
62
|
-
* @returns {screener}
|
|
63
|
-
*/
|
|
64
|
-
getScreener(exchange) {
|
|
65
|
-
const e = exchange.toUpperCase();
|
|
66
|
-
if (['NASDAQ', 'NYSE', 'NYSE ARCA', 'OTC'].includes(e)) return 'america';
|
|
67
|
-
if (['ASX'].includes(e)) return 'australia';
|
|
68
|
-
if (['TSX', 'TSXV', 'CSE', 'NEO'].includes(e)) return 'canada';
|
|
69
|
-
if (['EGX'].includes(e)) return 'egypt';
|
|
70
|
-
if (['FWB', 'SWB', 'XETR'].includes(e)) return 'germany';
|
|
71
|
-
if (['BSE', 'NSE'].includes(e)) return 'india';
|
|
72
|
-
if (['TASE'].includes(e)) return 'israel';
|
|
73
|
-
if (['MIL', 'MILSEDEX'].includes(e)) return 'italy';
|
|
74
|
-
if (['LUXSE'].includes(e)) return 'luxembourg';
|
|
75
|
-
if (['NEWCONNECT'].includes(e)) return 'poland';
|
|
76
|
-
if (['NGM'].includes(e)) return 'sweden';
|
|
77
|
-
if (['BIST'].includes(e)) return 'turkey';
|
|
78
|
-
if (['LSE', 'LSIN'].includes(e)) return 'uk';
|
|
79
|
-
if (['HNX'].includes(e)) return 'vietnam';
|
|
80
|
-
return exchange.toLowerCase();
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
/** Get technical analysis
|
|
84
|
-
* @param {screener} screener
|
|
85
|
-
* @param {string} id Full market id (Example: COINBASE:BTCEUR)
|
|
86
|
-
* @returns {Promise<Periods>} results
|
|
87
|
-
*/
|
|
88
|
-
async getTA(screener, id) {
|
|
89
|
-
const advice = {};
|
|
90
|
-
|
|
91
|
-
const cols = ['1', '5', '15', '60', '240', '1D', '1W', '1M']
|
|
92
|
-
.map((t) => indicators.map((i) => (t !== '1D' ? `${i}|${t}` : i)))
|
|
93
|
-
.flat();
|
|
94
|
-
|
|
95
|
-
const rs = await fetchScanData([id], screener, cols);
|
|
96
|
-
if (!rs.data || !rs.data[0]) return false;
|
|
97
|
-
|
|
98
|
-
rs.data[0].d.forEach((val, i) => {
|
|
99
|
-
const [name, period] = cols[i].split('|');
|
|
100
|
-
const pName = period || '1D';
|
|
101
|
-
if (!advice[pName]) advice[pName] = {};
|
|
102
|
-
advice[pName][name.split('.').pop()] = Math.round(val * 1000) / 500;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return advice;
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* @typedef {Object} SearchResult
|
|
110
|
-
* @property {string} id
|
|
111
|
-
* @property {string} exchange
|
|
112
|
-
* @property {string} fullExchange
|
|
113
|
-
* @property {string} screener
|
|
114
|
-
* @property {string} symbol
|
|
115
|
-
* @property {string} description
|
|
116
|
-
* @property {string} type
|
|
117
|
-
* @property {() => Promise<Periods>} getTA
|
|
118
|
-
*/
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @param {string} search Search a symbol
|
|
122
|
-
* @param {'stock' | 'futures' | 'forex' | 'cfd' | 'crypto' | 'index' | 'economic'} filter
|
|
123
|
-
* @returns {Promise<SearchResult[]>} Search results
|
|
124
|
-
*/
|
|
125
|
-
async search(search, filter = '') {
|
|
126
|
-
return new Promise((cb, err) => {
|
|
127
|
-
https.get({
|
|
128
|
-
host: 'symbol-search.tradingview.com',
|
|
129
|
-
path: `/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
|
|
130
|
-
origin: 'https://www.tradingview.com',
|
|
131
|
-
}, (res) => {
|
|
132
|
-
let rs = '';
|
|
133
|
-
res.on('data', (d) => { rs += d; });
|
|
134
|
-
res.on('end', () => {
|
|
135
|
-
try {
|
|
136
|
-
rs = JSON.parse(rs);
|
|
137
|
-
} catch (e) {
|
|
138
|
-
err(new Error('Can\'t parse server response'));
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
cb(rs.map((s) => {
|
|
143
|
-
const exchange = s.exchange.split(' ')[0];
|
|
144
|
-
const id = `${exchange}:${s.symbol}`;
|
|
145
|
-
|
|
146
|
-
const screener = (['forex', 'crypto'].includes(s.type)
|
|
147
|
-
? s.type
|
|
148
|
-
: this.getScreener(exchange)
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
id,
|
|
153
|
-
exchange,
|
|
154
|
-
fullExchange: s.exchange,
|
|
155
|
-
screener,
|
|
156
|
-
symbol: s.symbol,
|
|
157
|
-
description: s.description,
|
|
158
|
-
type: s.type,
|
|
159
|
-
getTA: () => this.getTA(screener, id),
|
|
160
|
-
};
|
|
161
|
-
}));
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
res.on('error', err);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* @typedef {Object} indicatorInput
|
|
171
|
-
* @property {string} name
|
|
172
|
-
* @property {string} inline Inline name
|
|
173
|
-
* @property { 'text' | 'source' | 'integer' | 'float' | 'resolution' | 'bool' } type
|
|
174
|
-
* @property {string | number | boolean} value
|
|
175
|
-
* @property {string | number | boolean} defVal
|
|
176
|
-
* @property {boolean} hidden
|
|
177
|
-
* @property {boolean} isFake
|
|
178
|
-
* @property {string[]} [options]
|
|
179
|
-
*/
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* @typedef {Object} Indicator
|
|
183
|
-
* @property {string} pineId Indicator ID
|
|
184
|
-
* @property {string} pineVersion Indicator version
|
|
185
|
-
* @property {string} description Indicator description
|
|
186
|
-
* @property {string} shortDescription Indicator short description
|
|
187
|
-
* @property {Object<string, indicatorInput>} inputs Indicator inputs
|
|
188
|
-
* @property {Object<string, string>} plots Indicator plots
|
|
189
|
-
* @property {string} script Indicator script
|
|
190
|
-
*/
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get an indicator
|
|
194
|
-
* @param {string} id Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
|
|
195
|
-
* @param {string} version Wanted version of the indicator
|
|
196
|
-
* @returns {Promise<Indicator>} Indicator
|
|
197
|
-
*/
|
|
198
|
-
async getIndicator(id, version, settings = []) {
|
|
199
|
-
return new Promise((cb, err) => {
|
|
200
|
-
https.get(`https://pine-facade.tradingview.com/pine-facade/translate/${id}/${version}`, (res) => {
|
|
201
|
-
let rs = '';
|
|
202
|
-
res.on('data', (d) => { rs += d; });
|
|
203
|
-
res.on('end', () => {
|
|
204
|
-
try {
|
|
205
|
-
rs = JSON.parse(rs);
|
|
206
|
-
} catch (e) {
|
|
207
|
-
err(new Error('Inexistent or unsupported indicator'));
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (!rs.success || !rs.result.metaInfo || !rs.result.metaInfo.inputs) {
|
|
212
|
-
err(new Error('Inexistent or unsupported indicator'));
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const inputs = {};
|
|
217
|
-
|
|
218
|
-
rs.result.metaInfo.inputs.forEach((input) => {
|
|
219
|
-
if (['text', 'pineId', 'pineVersion'].includes(input.id)) return;
|
|
220
|
-
const inline = input.inline || input.name.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
|
|
221
|
-
|
|
222
|
-
const i = parseInt(input.id.replace(/[^0-9]/g, ''), 10);
|
|
223
|
-
|
|
224
|
-
inputs[input.id] = {
|
|
225
|
-
name: input.name,
|
|
226
|
-
type: input.type,
|
|
227
|
-
value: settings[i] ?? input.defval,
|
|
228
|
-
defVal: input.defval,
|
|
229
|
-
hidden: !!input.isHidden,
|
|
230
|
-
isFake: !!input.isFake,
|
|
231
|
-
inline,
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
if (input.options) inputs[input.id].options = input.options;
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const plots = {};
|
|
238
|
-
|
|
239
|
-
Object.keys(rs.result.metaInfo.styles).forEach((plotId) => {
|
|
240
|
-
plots[plotId] = rs.result.metaInfo.styles[plotId].title.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
cb({
|
|
244
|
-
pineId: id,
|
|
245
|
-
pineVersion: version,
|
|
246
|
-
description: rs.result.metaInfo.description,
|
|
247
|
-
shortDescription: rs.result.metaInfo.shortDescription,
|
|
248
|
-
inputs,
|
|
249
|
-
plots,
|
|
250
|
-
script: rs.result.ilTemplate,
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
res.on('error', err);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Get a token for an user from a 'sessionid' cookie
|
|
261
|
-
* @param {string} session User 'sessionid' cookie
|
|
262
|
-
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
|
|
263
|
-
* @returns {Promise<string>} Token
|
|
264
|
-
*/
|
|
265
|
-
async getUserToken(session, location = 'https://www.tradingview.com/') {
|
|
266
|
-
return new Promise((cb, err) => {
|
|
267
|
-
https.get(location, {
|
|
268
|
-
headers: { cookie: `sessionid=${session}` },
|
|
269
|
-
}, (res) => {
|
|
270
|
-
let rs = '';
|
|
271
|
-
res.on('data', (d) => { rs += d; });
|
|
272
|
-
res.on('end', async () => {
|
|
273
|
-
if (res.headers.location && location !== res.headers.location) {
|
|
274
|
-
cb(await module.exports.getUserToken(session, res.headers.location));
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
if (rs.includes('auth_token')) cb(/"auth_token":"(.*?)"/g.exec(rs)[1]);
|
|
278
|
-
else err(new Error('Wrong or expired sessionid'));
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
res.on('error', err);
|
|
282
|
-
}).end();
|
|
283
|
-
});
|
|
284
|
-
},
|
|
285
|
-
};
|