@mathieuc/tradingview 2.0.1 → 2.0.7
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/.github/FUNDING.yml +1 -0
- package/README.md +3 -3
- package/main.js +41 -10
- package/miscRequests.js +420 -258
- package/package.json +5 -4
- package/tests/alerts/drawings.js +41 -0
- package/tests/alerts/{alerts.js → manualAlert.js} +4 -19
- package/tests/alerts/sentNotifs.js +16 -0
- package/tests/alerts/strategyAlert.js +94 -0
- package/tests/analysis.js +13 -13
- package/tests/alerts/.env +0 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
github: Mathieu2301
|
package/README.md
CHANGED
|
@@ -56,9 +56,9 @@ const marketAPI = require('tradingview');
|
|
|
56
56
|
|
|
57
57
|
const marketAPI = require('tradingview');
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
(async () => {
|
|
60
|
+
const market = marketAPI(false);
|
|
60
61
|
|
|
61
|
-
market.on('logged', async () => {
|
|
62
62
|
const searchBTC = (await market.search('bitcoin euro', 'crypto'))[0];
|
|
63
63
|
console.log('Found Bitcoin / Euro:', searchBTC);
|
|
64
64
|
|
|
@@ -66,7 +66,7 @@ market.on('logged', async () => {
|
|
|
66
66
|
console.log('Full technical analysis for Bitcoin:', TA);
|
|
67
67
|
|
|
68
68
|
// You can also use this way: await market.getTA('crypto', 'BINANCE:BTCEUR');
|
|
69
|
-
});
|
|
69
|
+
})();
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
```javascript
|
package/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const WebSocket = require('ws');
|
|
2
2
|
const {
|
|
3
3
|
search, getScreener, getTA, getIndicator,
|
|
4
|
+
getUser, getChartToken, getDrawings,
|
|
4
5
|
} = require('./miscRequests');
|
|
5
6
|
|
|
6
7
|
let onPacket = () => null;
|
|
@@ -88,6 +89,9 @@ module.exports = (autoInit = true) => {
|
|
|
88
89
|
/** List of subscribed symbols */
|
|
89
90
|
let subscribed = [];
|
|
90
91
|
|
|
92
|
+
/** Websockets status calls */
|
|
93
|
+
let isEnded = false;
|
|
94
|
+
|
|
91
95
|
/**
|
|
92
96
|
* Send a custom packet
|
|
93
97
|
* @param {string} t Packet type
|
|
@@ -166,33 +170,56 @@ module.exports = (autoInit = true) => {
|
|
|
166
170
|
};
|
|
167
171
|
|
|
168
172
|
return {
|
|
169
|
-
/**
|
|
170
|
-
* @param {'connected' | 'disconnected' | 'logged'
|
|
171
|
-
* | 'subscribed' | 'price' | 'data' | 'error' | 'ping' }
|
|
172
|
-
* @param {(...data: object) => null} cb
|
|
173
|
+
/** Event listener
|
|
174
|
+
* @param { 'connected' | 'disconnected' | 'logged'
|
|
175
|
+
* | 'subscribed' | 'price' | 'data' | 'error' | 'ping' } event Event
|
|
176
|
+
* @param {(...data: object) => null} cb Callback
|
|
173
177
|
*/
|
|
174
|
-
on(
|
|
175
|
-
if (!callbacks[
|
|
176
|
-
console.log('Wrong event:',
|
|
178
|
+
on(event, cb) {
|
|
179
|
+
if (!callbacks[event]) {
|
|
180
|
+
console.log('Wrong event:', event);
|
|
177
181
|
console.log('Available events:', Object.keys(callbacks));
|
|
178
182
|
return;
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
callbacks[
|
|
185
|
+
callbacks[event].push(cb);
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Close the websocket connection
|
|
190
|
+
* @return {Promise<void>} When websocket is closed
|
|
191
|
+
*/
|
|
192
|
+
end() {
|
|
193
|
+
return new Promise((cb) => {
|
|
194
|
+
isEnded = true;
|
|
195
|
+
ws.close();
|
|
196
|
+
cb();
|
|
197
|
+
});
|
|
182
198
|
},
|
|
183
199
|
|
|
200
|
+
getUser,
|
|
201
|
+
getChartToken,
|
|
202
|
+
getDrawings,
|
|
184
203
|
search,
|
|
185
204
|
getScreener,
|
|
186
205
|
getTA,
|
|
187
206
|
subscribed,
|
|
188
207
|
|
|
189
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Unsubscribe to a market
|
|
210
|
+
* @param {string} symbol Market symbol (Example: BTCEUR or COINBASE:BTCEUR)
|
|
211
|
+
*/
|
|
212
|
+
subscribe(symbol) {
|
|
190
213
|
if (subscribed.includes(symbol)) return;
|
|
191
214
|
send('quote_add_symbols', [sessionId, symbol]);
|
|
192
215
|
subscribed.push(symbol);
|
|
193
216
|
},
|
|
194
217
|
|
|
195
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Unsubscribe from a market
|
|
220
|
+
* @param {string} symbol Market symbol (Example: BTCEUR or COINBASE:BTCEUR)
|
|
221
|
+
*/
|
|
222
|
+
unsubscribe(symbol) {
|
|
196
223
|
if (!subscribed.includes(symbol)) return;
|
|
197
224
|
send('quote_remove_symbols', [sessionId, symbol]);
|
|
198
225
|
subscribed = subscribed.filter((s) => s !== symbol);
|
|
@@ -205,6 +232,7 @@ module.exports = (autoInit = true) => {
|
|
|
205
232
|
* @property {(string | number | boolean | null)[]} [settings] Indicator settings value
|
|
206
233
|
*
|
|
207
234
|
* @typedef {Object} ChartInfos
|
|
235
|
+
* @property {string} [session] User 'sessionid' cookie
|
|
208
236
|
* @property {string} symbol Market symbol (Example: BTCEUR or COINBASE:BTCEUR)
|
|
209
237
|
* @property { '1' | '3' | '5' | '15' | '30' | '45'
|
|
210
238
|
* | '60' | '120' | '180' | '240'
|
|
@@ -271,6 +299,8 @@ module.exports = (autoInit = true) => {
|
|
|
271
299
|
}
|
|
272
300
|
|
|
273
301
|
chartCBs[chartSession] = (packet) => {
|
|
302
|
+
if (isEnded) return;
|
|
303
|
+
|
|
274
304
|
if (['timescale_update', 'du'].includes(packet.type)) {
|
|
275
305
|
updatePeriods(packet);
|
|
276
306
|
onUpdate([...periods].reverse());
|
|
@@ -282,6 +312,7 @@ module.exports = (autoInit = true) => {
|
|
|
282
312
|
}
|
|
283
313
|
};
|
|
284
314
|
|
|
315
|
+
if (chart.session) send('set_auth_token', [(await getUser(chart.session)).authToken]);
|
|
285
316
|
send('chart_create_session', [chartSession, '']);
|
|
286
317
|
if (chart.timezone) send('switch_timezone', [chartSession, chart.timezone]);
|
|
287
318
|
send('resolve_symbol', [chartSession, 'sds_sym_1', `={"symbol":"${chart.symbol || 'BTCEUR'}","adjustment":"splits"}`]);
|
package/miscRequests.js
CHANGED
|
@@ -1,258 +1,420 @@
|
|
|
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
|
-
/**
|
|
84
|
-
*
|
|
85
|
-
* @param {
|
|
86
|
-
* @
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
advice[pName][
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
* @
|
|
111
|
-
* @property {string}
|
|
112
|
-
* @property {string}
|
|
113
|
-
* @property {string}
|
|
114
|
-
* @property {string}
|
|
115
|
-
* @property {string}
|
|
116
|
-
* @property {string}
|
|
117
|
-
* @property {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*
|
|
123
|
-
* @
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
* @
|
|
173
|
-
* @property {
|
|
174
|
-
* @property {string
|
|
175
|
-
* @property {
|
|
176
|
-
* @property {boolean}
|
|
177
|
-
* @property {boolean}
|
|
178
|
-
* @property {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
* @
|
|
185
|
-
* @property {string}
|
|
186
|
-
* @property {string}
|
|
187
|
-
* @property {
|
|
188
|
-
* @property {
|
|
189
|
-
* @property {string}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
*
|
|
196
|
-
* @
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
};
|
|
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
|
+
/**
|
|
84
|
+
* Get technical analysis
|
|
85
|
+
* @param {screener} screener
|
|
86
|
+
* @param {string} id Full market id (Example: COINBASE:BTCEUR)
|
|
87
|
+
* @returns {Promise<Periods>} results
|
|
88
|
+
*/
|
|
89
|
+
async getTA(screener, id) {
|
|
90
|
+
const advice = {};
|
|
91
|
+
|
|
92
|
+
const cols = ['1', '5', '15', '60', '240', '1D', '1W', '1M']
|
|
93
|
+
.map((t) => indicators.map((i) => (t !== '1D' ? `${i}|${t}` : i)))
|
|
94
|
+
.flat();
|
|
95
|
+
|
|
96
|
+
const rs = await fetchScanData([id], screener, cols);
|
|
97
|
+
if (!rs.data || !rs.data[0]) return false;
|
|
98
|
+
|
|
99
|
+
rs.data[0].d.forEach((val, i) => {
|
|
100
|
+
const [name, period] = cols[i].split('|');
|
|
101
|
+
const pName = period || '1D';
|
|
102
|
+
if (!advice[pName]) advice[pName] = {};
|
|
103
|
+
advice[pName][name.split('.').pop()] = Math.round(val * 1000) / 500;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return advice;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @typedef {Object} SearchResult
|
|
111
|
+
* @property {string} id
|
|
112
|
+
* @property {string} exchange
|
|
113
|
+
* @property {string} fullExchange
|
|
114
|
+
* @property {string} screener
|
|
115
|
+
* @property {string} symbol
|
|
116
|
+
* @property {string} description
|
|
117
|
+
* @property {string} type
|
|
118
|
+
* @property {() => Promise<Periods>} getTA
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Find a symbol
|
|
123
|
+
* @param {string} search Keywords
|
|
124
|
+
* @param {'stock' | 'futures' | 'forex' | 'cfd' | 'crypto' | 'index' | 'economic'} [filter]
|
|
125
|
+
* @returns {Promise<SearchResult[]>} Search results
|
|
126
|
+
*/
|
|
127
|
+
async search(search, filter = '') {
|
|
128
|
+
return new Promise((cb, err) => {
|
|
129
|
+
https.get({
|
|
130
|
+
host: 'symbol-search.tradingview.com',
|
|
131
|
+
path: `/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
|
|
132
|
+
origin: 'https://www.tradingview.com',
|
|
133
|
+
}, (res) => {
|
|
134
|
+
let rs = '';
|
|
135
|
+
res.on('data', (d) => { rs += d; });
|
|
136
|
+
res.on('end', () => {
|
|
137
|
+
try {
|
|
138
|
+
rs = JSON.parse(rs);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
err(new Error('Can\'t parse server response'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
cb(rs.map((s) => {
|
|
145
|
+
const exchange = s.exchange.split(' ')[0];
|
|
146
|
+
const id = `${exchange}:${s.symbol}`;
|
|
147
|
+
|
|
148
|
+
const screener = (['forex', 'crypto'].includes(s.type)
|
|
149
|
+
? s.type
|
|
150
|
+
: this.getScreener(exchange)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
id,
|
|
155
|
+
exchange,
|
|
156
|
+
fullExchange: s.exchange,
|
|
157
|
+
screener,
|
|
158
|
+
symbol: s.symbol,
|
|
159
|
+
description: s.description,
|
|
160
|
+
type: s.type,
|
|
161
|
+
getTA: () => this.getTA(screener, id),
|
|
162
|
+
};
|
|
163
|
+
}));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
res.on('error', err);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @typedef {Object} indicatorInput
|
|
173
|
+
* @property {string} name
|
|
174
|
+
* @property {string} inline Inline name
|
|
175
|
+
* @property { 'text' | 'source' | 'integer' | 'float' | 'resolution' | 'bool' } type
|
|
176
|
+
* @property {string | number | boolean} value
|
|
177
|
+
* @property {string | number | boolean} defVal
|
|
178
|
+
* @property {boolean} hidden
|
|
179
|
+
* @property {boolean} isFake
|
|
180
|
+
* @property {string[]} [options]
|
|
181
|
+
*/
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @typedef {Object} Indicator
|
|
185
|
+
* @property {string} pineId Indicator ID
|
|
186
|
+
* @property {string} pineVersion Indicator version
|
|
187
|
+
* @property {string} description Indicator description
|
|
188
|
+
* @property {string} shortDescription Indicator short description
|
|
189
|
+
* @property {Object<string, indicatorInput>} inputs Indicator inputs
|
|
190
|
+
* @property {Object<string, string>} plots Indicator plots
|
|
191
|
+
* @property {string} script Indicator script
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get an indicator
|
|
196
|
+
* @param {string} id Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
|
|
197
|
+
* @param {string} version Wanted version of the indicator
|
|
198
|
+
* @returns {Promise<Indicator>} Indicator
|
|
199
|
+
*/
|
|
200
|
+
async getIndicator(id, version, settings = []) {
|
|
201
|
+
return new Promise((cb, err) => {
|
|
202
|
+
https.get(`https://pine-facade.tradingview.com/pine-facade/translate/${id}/${version}`, (res) => {
|
|
203
|
+
let rs = '';
|
|
204
|
+
res.on('data', (d) => { rs += d; });
|
|
205
|
+
res.on('end', () => {
|
|
206
|
+
try {
|
|
207
|
+
rs = JSON.parse(rs);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
err(new Error('Inexistent or unsupported indicator'));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!rs.success || !rs.result.metaInfo || !rs.result.metaInfo.inputs) {
|
|
214
|
+
err(new Error('Inexistent or unsupported indicator'));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const inputs = {};
|
|
219
|
+
|
|
220
|
+
rs.result.metaInfo.inputs.forEach((input) => {
|
|
221
|
+
if (['text', 'pineId', 'pineVersion'].includes(input.id)) return;
|
|
222
|
+
const inline = input.inline || input.name.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
|
|
223
|
+
|
|
224
|
+
const i = parseInt(input.id.replace(/[^0-9]/g, ''), 10);
|
|
225
|
+
|
|
226
|
+
inputs[input.id] = {
|
|
227
|
+
name: input.name,
|
|
228
|
+
type: input.type,
|
|
229
|
+
value: settings[i] ?? input.defval,
|
|
230
|
+
defVal: input.defval,
|
|
231
|
+
hidden: !!input.isHidden,
|
|
232
|
+
isFake: !!input.isFake,
|
|
233
|
+
inline,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (input.options) inputs[input.id].options = input.options;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const plots = {};
|
|
240
|
+
|
|
241
|
+
Object.keys(rs.result.metaInfo.styles).forEach((plotId) => {
|
|
242
|
+
plots[plotId] = rs.result.metaInfo.styles[plotId].title.replace(/ /g, '_').replace(/[^a-zA-Z0-9]/g, '');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
cb({
|
|
246
|
+
pineId: id,
|
|
247
|
+
pineVersion: version,
|
|
248
|
+
description: rs.result.metaInfo.description,
|
|
249
|
+
shortDescription: rs.result.metaInfo.shortDescription,
|
|
250
|
+
inputs,
|
|
251
|
+
plots,
|
|
252
|
+
script: rs.result.ilTemplate,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
res.on('error', err);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @typedef {Object} User Instance of User
|
|
263
|
+
* @property {number} id User ID
|
|
264
|
+
* @property {string} username User username
|
|
265
|
+
* @property {string} firstName User first name
|
|
266
|
+
* @property {string} lastName User last name
|
|
267
|
+
* @property {number} reputation User reputation
|
|
268
|
+
* @property {number} following Number of following accounts
|
|
269
|
+
* @property {number} followers Number of followers
|
|
270
|
+
* @property {Object} notifications User's notifications
|
|
271
|
+
* @property {number} notifications.user User notifications
|
|
272
|
+
* @property {number} notifications.following Notification from following accounts
|
|
273
|
+
* @property {string} session User session
|
|
274
|
+
* @property {string} sessionHash User session hash
|
|
275
|
+
* @property {string} privateChannel User private channel
|
|
276
|
+
* @property {string} authToken User auth token
|
|
277
|
+
* @property {Date} joinDate Account creation date
|
|
278
|
+
*/
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get a token for an user from a 'sessionid' cookie
|
|
282
|
+
* @param {string} session User 'sessionid' cookie
|
|
283
|
+
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
|
|
284
|
+
* @returns {Promise<User>} Token
|
|
285
|
+
*/
|
|
286
|
+
async getUser(session, location = 'https://www.tradingview.com/') {
|
|
287
|
+
return new Promise((cb, err) => {
|
|
288
|
+
https.get(location, {
|
|
289
|
+
headers: { cookie: `sessionid=${session}` },
|
|
290
|
+
}, (res) => {
|
|
291
|
+
let rs = '';
|
|
292
|
+
res.on('data', (d) => { rs += d; });
|
|
293
|
+
res.on('end', async () => {
|
|
294
|
+
if (res.headers.location && location !== res.headers.location) {
|
|
295
|
+
cb(await module.exports.getUser(session, res.headers.location));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (rs.includes('auth_token')) {
|
|
299
|
+
cb({
|
|
300
|
+
id: /"id":([0-9]{1,10}),/.exec(rs)[1],
|
|
301
|
+
username: /"username":"(.*?)"/.exec(rs)[1],
|
|
302
|
+
firstName: /"first_name":"(.*?)"/.exec(rs)[1],
|
|
303
|
+
lastName: /"last_name":"(.*?)"/.exec(rs)[1],
|
|
304
|
+
reputation: parseFloat(/"reputation":(.*?),/.exec(rs)[1] || 0),
|
|
305
|
+
following: parseFloat(/,"following":([0-9]*?),/.exec(rs)[1] || 0),
|
|
306
|
+
followers: parseFloat(/,"followers":([0-9]*?),/.exec(rs)[1] || 0),
|
|
307
|
+
notifications: {
|
|
308
|
+
following: parseFloat(/"notification_count":\{"following":([0-9]*),/.exec(rs)[1] || 0),
|
|
309
|
+
user: parseFloat(/"notification_count":\{"following":[0-9]*,"user":([0-9]*)/.exec(rs)[1] || 0),
|
|
310
|
+
},
|
|
311
|
+
session,
|
|
312
|
+
sessionHash: /"session_hash":"(.*?)"/.exec(rs)[1],
|
|
313
|
+
privateChannel: /"private_channel":"(.*?)"/.exec(rs)[1],
|
|
314
|
+
authToken: /"auth_token":"(.*?)"/.exec(rs)[1],
|
|
315
|
+
joinDate: new Date(/"date_joined":"(.*?)"/.exec(rs)[1] || 0),
|
|
316
|
+
});
|
|
317
|
+
} else err(new Error('Wrong or expired sessionid'));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
res.on('error', err);
|
|
321
|
+
}).end();
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* User credentials
|
|
327
|
+
* @typedef {Object} UserCredentials
|
|
328
|
+
* @property {number} id User ID
|
|
329
|
+
* @property {string} session User session ('sessionid' cookie)
|
|
330
|
+
*/
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
334
|
+
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
335
|
+
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
336
|
+
* @returns {Promise<string>} Token
|
|
337
|
+
*/
|
|
338
|
+
async getChartToken(layout, credentials = {}) {
|
|
339
|
+
return new Promise((cb, err) => {
|
|
340
|
+
const creds = credentials.id && credentials.session;
|
|
341
|
+
const userID = creds ? credentials.id : -1;
|
|
342
|
+
const session = creds ? credentials.session : null;
|
|
343
|
+
|
|
344
|
+
https.get(`https://www.tradingview.com/chart-token/?image_url=${layout}&user_id=${userID}`, {
|
|
345
|
+
headers: { cookie: session ? `sessionid=${session}` : '' },
|
|
346
|
+
}, (res) => {
|
|
347
|
+
let rs = '';
|
|
348
|
+
res.on('data', (d) => { rs += d; });
|
|
349
|
+
res.on('end', async () => {
|
|
350
|
+
rs = JSON.parse(rs);
|
|
351
|
+
|
|
352
|
+
if (rs.token) cb(rs.token);
|
|
353
|
+
else err(new Error('Wrong layout or credentials'));
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
res.on('error', err);
|
|
357
|
+
}).end();
|
|
358
|
+
});
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @typedef {Object} DrawingPoint
|
|
363
|
+
* @property {number} time_t Point X time position
|
|
364
|
+
* @property {number} price Point Y price position
|
|
365
|
+
* @property {number} offset Point offset
|
|
366
|
+
*/
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* @typedef {Object} Drawing
|
|
370
|
+
* @property {string} id Drawing ID (Like: 'XXXXXX')
|
|
371
|
+
* @property {string} symbol Layout market symbol (Like: 'BINANCE:BUCEUR')
|
|
372
|
+
* @property {string} ownerSource Owner user ID (Like: 'XXXXXX')
|
|
373
|
+
* @property {string} serverUpdateTime Drawing last update timestamp
|
|
374
|
+
* @property {string} currencyId Currency ID (Like: 'EUR')
|
|
375
|
+
* @property {any} unitId Unit ID
|
|
376
|
+
* @property {string} type Drawing type
|
|
377
|
+
* @property {DrawingPoint[]} points List of drawing points
|
|
378
|
+
* @property {number} zorder Drawing Z order
|
|
379
|
+
* @property {string} linkKey Drawing link key
|
|
380
|
+
* @property {Object} state Drawing state
|
|
381
|
+
*/
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
385
|
+
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
386
|
+
* @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
|
|
387
|
+
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
388
|
+
* @param {number} [chartID = 1] Chart ID
|
|
389
|
+
* @returns {Promise<Drawing[]>} Drawings
|
|
390
|
+
*/
|
|
391
|
+
async getDrawings(layout, symbol = '', credentials = {}, chartID = 1) {
|
|
392
|
+
const chartToken = await module.exports.getChartToken(layout, credentials);
|
|
393
|
+
|
|
394
|
+
return new Promise((cb, err) => {
|
|
395
|
+
const creds = credentials.id && credentials.session;
|
|
396
|
+
const session = creds ? credentials.session : null;
|
|
397
|
+
|
|
398
|
+
const url = `https://charts-storage.tradingview.com/charts-storage/layout/${
|
|
399
|
+
layout}/sources?chart_id=${chartID}&jwt=${chartToken}${symbol ? `&symbol=${symbol}` : ''}`;
|
|
400
|
+
|
|
401
|
+
https.get(url, {
|
|
402
|
+
headers: { cookie: session ? `sessionid=${session}` : '' },
|
|
403
|
+
}, (res) => {
|
|
404
|
+
let rs = '';
|
|
405
|
+
res.on('data', (d) => { rs += d; });
|
|
406
|
+
res.on('end', async () => {
|
|
407
|
+
rs = JSON.parse(rs);
|
|
408
|
+
|
|
409
|
+
if (rs.payload) {
|
|
410
|
+
cb(Object.values(rs.payload.sources || {}).map((drawing) => ({
|
|
411
|
+
...drawing, ...drawing.state,
|
|
412
|
+
})));
|
|
413
|
+
} else err(new Error('Wrong layout, user credentials, or chart id.'));
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
res.on('error', err);
|
|
417
|
+
}).end();
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mathieuc/tradingview",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Tradingview instant stocks API",
|
|
3
|
+
"version": "2.0.7",
|
|
4
|
+
"description": "Tradingview instant stocks API, indicator alerts, trading bot, and more !",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test-prices": "node ./tests/prices.js",
|
|
8
8
|
"test-analysis": "node ./tests/analysis.js",
|
|
9
9
|
"test-indicator": "node ./tests/indicator.js",
|
|
10
|
-
"test-alerts": "node ./tests/alerts/
|
|
11
|
-
"
|
|
10
|
+
"test-manual-alerts": "node ./tests/alerts/manualAlert.js",
|
|
11
|
+
"test-strat-alerts": "node ./tests/alerts/strategyAlert.js",
|
|
12
|
+
"test": "npm run test-prices"
|
|
12
13
|
},
|
|
13
14
|
"keywords": [
|
|
14
15
|
"tradingwiew",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require('./envLoader');
|
|
2
|
+
const marketAPI = require('../../main');
|
|
3
|
+
|
|
4
|
+
if (!process.env.LAYOUT_ID) console.error('You need to set a layout id');
|
|
5
|
+
|
|
6
|
+
const market = marketAPI(false);
|
|
7
|
+
|
|
8
|
+
console.log('Connecting...');
|
|
9
|
+
|
|
10
|
+
(async () => {
|
|
11
|
+
/*
|
|
12
|
+
* Important ! If you want to get a layout without login, you have to
|
|
13
|
+
* set it in public mode. If you get an error, please wait few minutes.
|
|
14
|
+
* Try getting to https://www.tradingview.com/chart/LAYOUT_ID/ in incognito mode.
|
|
15
|
+
*/
|
|
16
|
+
const user = (process.env.SESSION_ID) ? await market.getUser(process.env.SESSION_ID) : {};
|
|
17
|
+
console.log('Connected as', user.firstName || user.username || 'anonymous', '!');
|
|
18
|
+
|
|
19
|
+
console.log(`Gettings drawings of '${process.env.LAYOUT_ID}' layout...`);
|
|
20
|
+
|
|
21
|
+
const drawings = await market.getDrawings(process.env.LAYOUT_ID, '', {
|
|
22
|
+
id: user.id,
|
|
23
|
+
session: user.session,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const lines = drawings.filter((d) => (
|
|
27
|
+
['LineToolHorzLine', 'LineToolRay', 'LineToolTrendLine', 'LineToolHorzRay'].includes(d.type)
|
|
28
|
+
&& d.state.linestyle === 0
|
|
29
|
+
&& d.state.linewidth === 4
|
|
30
|
+
));
|
|
31
|
+
|
|
32
|
+
console.log(
|
|
33
|
+
`Found ${drawings.length} result(s) including ${lines.length} thick line(s):`,
|
|
34
|
+
lines.map((l) => ({
|
|
35
|
+
market: l.symbol,
|
|
36
|
+
type: l.type,
|
|
37
|
+
points: l.points,
|
|
38
|
+
text: l.state.text,
|
|
39
|
+
})),
|
|
40
|
+
);
|
|
41
|
+
})();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require('./envLoader');
|
|
2
|
+
const { getSentNotifs, addSentNotifs } = require('./sentNotifs');
|
|
2
3
|
const marketAPI = require('../../main');
|
|
3
4
|
const discordWH = require('./discordWH')(process.env.DISCORD_TOKEN);
|
|
4
5
|
|
|
@@ -7,8 +8,6 @@ if (!process.env.PERIOD) process.env.PERIOD = '240';
|
|
|
7
8
|
|
|
8
9
|
const market = marketAPI(false);
|
|
9
10
|
|
|
10
|
-
const sentNotifs = [];
|
|
11
|
-
|
|
12
11
|
market.on('logged', () => {
|
|
13
12
|
process.env.SYMBOLS.split(',').forEach((symbol) => {
|
|
14
13
|
console.log('Listen', symbol);
|
|
@@ -39,20 +38,6 @@ market.on('logged', () => {
|
|
|
39
38
|
}, (periods) => {
|
|
40
39
|
if (!periods[0].CIPHER_B) return;
|
|
41
40
|
|
|
42
|
-
// periods.forEach((p, i) => {
|
|
43
|
-
// if (p.CIPHER_B.WTWave1 > 0 || p.CIPHER_B.WTWave1 < -35) return;
|
|
44
|
-
|
|
45
|
-
// const pBefore = periods[i + 1];
|
|
46
|
-
// if (!pBefore) return;
|
|
47
|
-
|
|
48
|
-
// const gapBefore = pBefore.CIPHER_B.VWAP;
|
|
49
|
-
// const gap = p.CIPHER_B.VWAP;
|
|
50
|
-
// if (
|
|
51
|
-
// gapBefore < 0 && gapBefore < gap
|
|
52
|
-
// && pBefore.CIPHER_B.WTWave1 < p.CIPHER_B.WTWave1
|
|
53
|
-
// ) console.log(symbol, 'TRIG', new Date(p.$time * 1000).toLocaleString());
|
|
54
|
-
// });
|
|
55
|
-
|
|
56
41
|
const pBefore = periods[1];
|
|
57
42
|
const p = periods[0];
|
|
58
43
|
|
|
@@ -62,12 +47,12 @@ market.on('logged', () => {
|
|
|
62
47
|
const notifID = `${symbol}@${p.$time}@BULL_TRIGGER`;
|
|
63
48
|
|
|
64
49
|
if (
|
|
65
|
-
|
|
66
|
-
&& p.CIPHER_B.WTWave1 <= 0 && p.CIPHER_B.WTWave1 > -35
|
|
50
|
+
p.CIPHER_B.WTWave1 <= 0 && p.CIPHER_B.WTWave1 > -35
|
|
67
51
|
&& gapBefore < 0 && gapBefore < gap
|
|
68
52
|
&& pBefore.CIPHER_B.WTWave1 < p.CIPHER_B.WTWave1
|
|
53
|
+
&& !getSentNotifs().includes(notifID)
|
|
69
54
|
) {
|
|
70
|
-
|
|
55
|
+
addSentNotifs(notifID);
|
|
71
56
|
console.log(symbol, 'TRIG NOW', new Date(p.$time * 1000).toLocaleString());
|
|
72
57
|
|
|
73
58
|
discordWH({
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
const sentNotifsFile = './sentNotifs.txt';
|
|
4
|
+
|
|
5
|
+
exports.getSentNotifs = () => {
|
|
6
|
+
if (!fs.existsSync(sentNotifsFile)) fs.writeFileSync(sentNotifsFile, '');
|
|
7
|
+
return (fs.readFileSync(sentNotifsFile, 'utf8') || '').split(';').filter((n) => n);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
exports.addSentNotifs = (notif = '') => {
|
|
11
|
+
if (!fs.existsSync(sentNotifsFile)) fs.writeFileSync(sentNotifsFile, '');
|
|
12
|
+
fs.writeFileSync(
|
|
13
|
+
sentNotifsFile,
|
|
14
|
+
`${fs.readFileSync(sentNotifsFile, 'utf8') || ''}${notif};`,
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require('./envLoader');
|
|
2
|
+
const { getSentNotifs, addSentNotifs } = require('./sentNotifs');
|
|
3
|
+
const marketAPI = require('../../main');
|
|
4
|
+
const discordWH = require('./discordWH')(process.env.DISCORD_TOKEN);
|
|
5
|
+
|
|
6
|
+
const markets = [
|
|
7
|
+
{
|
|
8
|
+
symbol: 'EURUSD',
|
|
9
|
+
period: '1D',
|
|
10
|
+
settings: {
|
|
11
|
+
1: 21, // HMA 1 Length
|
|
12
|
+
3: 89, // HMA 2 Length
|
|
13
|
+
5: 7, // RSI Length
|
|
14
|
+
8: 2, // Stop Loss (%)
|
|
15
|
+
9: 1, // Take Profit (%)
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
symbol: 'GBPUSD',
|
|
20
|
+
period: '1D',
|
|
21
|
+
settings: {
|
|
22
|
+
1: 220, // HMA 1 Length
|
|
23
|
+
3: 150, // HMA 2 Length
|
|
24
|
+
5: 1, // RSI Length
|
|
25
|
+
8: 2, // Stop Loss (%)
|
|
26
|
+
9: 1, // Take Profit (%)
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
symbol: 'USDCAD',
|
|
31
|
+
period: '1D',
|
|
32
|
+
settings: {
|
|
33
|
+
1: 21, // HMA 1 Length
|
|
34
|
+
3: 89, // HMA 2 Length
|
|
35
|
+
5: 10, // RSI Length
|
|
36
|
+
8: 3, // Stop Loss (%)
|
|
37
|
+
9: 2, // Take Profit (%)
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const market = marketAPI(false);
|
|
43
|
+
|
|
44
|
+
market.on('logged', () => {
|
|
45
|
+
markets.forEach((mrk) => {
|
|
46
|
+
console.log('Listen', mrk.symbol);
|
|
47
|
+
|
|
48
|
+
market.initChart({
|
|
49
|
+
symbol: mrk.symbol,
|
|
50
|
+
period: mrk.period || '1D',
|
|
51
|
+
range: mrk.range || 30,
|
|
52
|
+
timezone: mrk.timezone || 'Europe/Paris',
|
|
53
|
+
indicators: [
|
|
54
|
+
// My Magimix Indicator
|
|
55
|
+
{
|
|
56
|
+
name: 'MAGIMIX',
|
|
57
|
+
id: 'USER;d90a5e852ddc439b994db6beff14d702',
|
|
58
|
+
version: mrk.version || 'last',
|
|
59
|
+
settings: mrk.settings || [],
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
}, ({ 0: p }) => {
|
|
63
|
+
if (!p.MAGIMIX) return;
|
|
64
|
+
|
|
65
|
+
const signalType = p.MAGIMIX.Longsignal ? 'LONG' : 'SHORT';
|
|
66
|
+
const notifID = `${mrk.symbol}@MAGIMIX@${p.$time}@${signalType}`;
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
(p.MAGIMIX.Longsignal || p.MAGIMIX.Shortsignal)
|
|
70
|
+
&& !getSentNotifs().includes(notifID)
|
|
71
|
+
) {
|
|
72
|
+
addSentNotifs(notifID);
|
|
73
|
+
console.log(mrk.symbol, 'SIGNAL', new Date(p.$time * 1000).toLocaleString());
|
|
74
|
+
|
|
75
|
+
discordWH({
|
|
76
|
+
content: process.env.DISCORD_MENTION,
|
|
77
|
+
embeds: [
|
|
78
|
+
{
|
|
79
|
+
title: `Magimix ${signalType} signal for ${mrk.symbol} !`,
|
|
80
|
+
color: p.MAGIMIX.Longsignal ? 3066993 : 15158332,
|
|
81
|
+
description: `Magimix ${signalType} signal for ${mrk.symbol} at ${p.$prices.close} !`,
|
|
82
|
+
url: `https://fr.tradingview.com/chart/?symbol=${mrk.symbol}`,
|
|
83
|
+
timestamp: new Date(p.$time * 1000),
|
|
84
|
+
footer: {
|
|
85
|
+
text: 'TradingView',
|
|
86
|
+
icon_url: 'https://tradingview.com/favicon.ico',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
package/tests/analysis.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
const marketAPI = require('../main');
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const searchBTC = (await market.search('bitcoin euro', 'crypto'))[0];
|
|
7
|
-
console.log('Found Bitcoin / Euro:', searchBTC);
|
|
8
|
-
|
|
9
|
-
const TA = await searchBTC.getTA();
|
|
10
|
-
console.log('Full technical analysis for Bitcoin:', TA);
|
|
11
|
-
|
|
12
|
-
// You can also use this way: await market.getTA('crypto', 'BINANCE:BTCEUR');
|
|
13
|
-
});
|
|
1
|
+
const marketAPI = require('../main');
|
|
2
|
+
|
|
3
|
+
(async () => {
|
|
4
|
+
const market = marketAPI(false);
|
|
5
|
+
|
|
6
|
+
const searchBTC = (await market.search('bitcoin euro', 'crypto'))[0];
|
|
7
|
+
console.log('Found Bitcoin / Euro:', searchBTC);
|
|
8
|
+
|
|
9
|
+
const TA = await searchBTC.getTA();
|
|
10
|
+
console.log('Full technical analysis for Bitcoin:', TA);
|
|
11
|
+
|
|
12
|
+
// You can also use this way: await market.getTA('crypto', 'BINANCE:BTCEUR');
|
|
13
|
+
})();
|
package/tests/alerts/.env
DELETED