@mathieuc/tradingview 2.0.91 → 3.0.0-pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @typedef {'loaded' | 'data' | 'error'} MarketEvent
3
+ */
4
+
5
+ /**
6
+ * @param {import('./session').QuoteSessionBridge} quoteSession
7
+ */
8
+ module.exports = (quoteSession) => class QuoteMarket {
9
+ #symbolListeners = quoteSession.symbolListeners;
10
+
11
+ #symbol;
12
+
13
+ #symbolListenerID = 0;
14
+
15
+ #lastData = {};
16
+
17
+ #callbacks = {
18
+ loaded: [],
19
+ data: [],
20
+
21
+ event: [],
22
+ error: [],
23
+ };
24
+
25
+ /**
26
+ * @param {MarketEvent} ev Client event
27
+ * @param {...{}} data Packet data
28
+ */
29
+ #handleEvent(ev, ...data) {
30
+ this.#callbacks[ev].forEach((e) => e(...data));
31
+ this.#callbacks.event.forEach((e) => e(ev, ...data));
32
+ }
33
+
34
+ #handleError(...msgs) {
35
+ if (this.#callbacks.error.length === 0) console.error(...msgs);
36
+ else this.#handleEvent('error', ...msgs);
37
+ }
38
+
39
+ /**
40
+ * @param {string} symbol Market symbol (like: 'BTCEUR' or 'KRAKEN:BTCEUR')
41
+ */
42
+ constructor(symbol) {
43
+ this.#symbol = symbol;
44
+
45
+ if (!this.#symbolListeners[symbol]) {
46
+ this.#symbolListeners[symbol] = [];
47
+ quoteSession.send('quote_add_symbols', [
48
+ quoteSession.sessionID,
49
+ symbol,
50
+ ]);
51
+ }
52
+ this.#symbolListenerID = this.#symbolListeners[symbol].length;
53
+
54
+ this.#symbolListeners[symbol][this.#symbolListenerID] = (packet) => {
55
+ if (global.TW_DEBUG) console.log('§90§30§105 MARKET §0 DATA', packet);
56
+
57
+ if (packet.type === 'qsd' && packet.data[1].s === 'ok') {
58
+ this.#lastData = {
59
+ ...this.#lastData,
60
+ ...packet.data[1].v,
61
+ };
62
+ this.#handleEvent('data', this.#lastData);
63
+ return;
64
+ }
65
+
66
+ if (packet.type === 'quote_completed') {
67
+ this.#handleEvent('loaded');
68
+ return;
69
+ }
70
+
71
+ if (packet.type === 'qsd' && packet.data[1].s === 'error') {
72
+ this.#handleError('Market error', packet.data);
73
+ }
74
+ };
75
+ }
76
+
77
+ /**
78
+ * When quote market is loaded
79
+ * @param {() => void} cb Callback
80
+ * @event
81
+ */
82
+ onLoaded(cb) {
83
+ this.#callbacks.loaded.push(cb);
84
+ }
85
+
86
+ /**
87
+ * When quote data is received
88
+ * @param {(data: {}) => void} cb Callback
89
+ * @event
90
+ */
91
+ onData(cb) {
92
+ this.#callbacks.data.push(cb);
93
+ }
94
+
95
+ /**
96
+ * When quote event happens
97
+ * @param {(...any) => void} cb Callback
98
+ * @event
99
+ */
100
+ onEvent(cb) {
101
+ this.#callbacks.event.push(cb);
102
+ }
103
+
104
+ /**
105
+ * When quote error happens
106
+ * @param {(...any) => void} cb Callback
107
+ * @event
108
+ */
109
+ onError(cb) {
110
+ this.#callbacks.error.push(cb);
111
+ }
112
+
113
+ /** Close this listener */
114
+ close() {
115
+ if (this.#symbolListeners[this.#symbol].length <= 1) {
116
+ quoteSession.send('quote_remove_symbols', [
117
+ quoteSession.sessionID,
118
+ this.#symbol,
119
+ ]);
120
+ }
121
+ delete this.#symbolListeners[this.#symbol][this.#symbolListenerID];
122
+ }
123
+ };
@@ -0,0 +1,124 @@
1
+ const { genSessionID } = require('../utils');
2
+
3
+ const quoteMarketConstructor = require('./market');
4
+
5
+ /** @typedef {Object<string, Function[]>} SymbolListeners */
6
+
7
+ /**
8
+ * @typedef {Object} QuoteSessionBridge
9
+ * @prop {string} sessionID
10
+ * @prop {SymbolListeners} symbolListeners
11
+ * @prop {import('../client').SendPacket} send
12
+ */
13
+
14
+ /**
15
+ * @typedef {'base-currency-logoid'
16
+ * | 'ch' | 'chp' | 'currency-logoid' | 'provider_id'
17
+ * | 'currency_code' | 'current_session' | 'description'
18
+ * | 'exchange' | 'format' | 'fractional' | 'is_tradable'
19
+ * | 'language' | 'local_description' | 'logoid' | 'lp'
20
+ * | 'lp_time' | 'minmov' | 'minmove2' | 'original_name'
21
+ * | 'pricescale' | 'pro_name' | 'short_name' | 'type'
22
+ * | 'update_mode' | 'volume' | 'ask' | 'bid' | 'fundamentals'
23
+ * | 'high_price' | 'low_price' | 'open_price' | 'prev_close_price'
24
+ * | 'rch' | 'rchp' | 'rtc' | 'rtc_time' | 'status' | 'industry'
25
+ * | 'basic_eps_net_income' | 'beta_1_year' | 'market_cap_basic'
26
+ * | 'earnings_per_share_basic_ttm' | 'price_earnings_ttm'
27
+ * | 'sector' | 'dividends_yield' | 'timezone' | 'country_code'
28
+ * } quoteField Quote data field
29
+ */
30
+
31
+ /** @param {'all' | 'price'} fieldsType */
32
+ function getQuoteFields(fieldsType) {
33
+ if (fieldsType === 'price') {
34
+ return ['lp'];
35
+ }
36
+
37
+ return [
38
+ 'base-currency-logoid', 'ch', 'chp', 'currency-logoid',
39
+ 'currency_code', 'current_session', 'description',
40
+ 'exchange', 'format', 'fractional', 'is_tradable',
41
+ 'language', 'local_description', 'logoid', 'lp',
42
+ 'lp_time', 'minmov', 'minmove2', 'original_name',
43
+ 'pricescale', 'pro_name', 'short_name', 'type',
44
+ 'update_mode', 'volume', 'ask', 'bid', 'fundamentals',
45
+ 'high_price', 'low_price', 'open_price', 'prev_close_price',
46
+ 'rch', 'rchp', 'rtc', 'rtc_time', 'status', 'industry',
47
+ 'basic_eps_net_income', 'beta_1_year', 'market_cap_basic',
48
+ 'earnings_per_share_basic_ttm', 'price_earnings_ttm',
49
+ 'sector', 'dividends_yield', 'timezone', 'country_code',
50
+ 'provider_id',
51
+ ];
52
+ }
53
+
54
+ /**
55
+ * @param {import('../client').ClientBridge} client
56
+ */
57
+ module.exports = (client) => class QuoteSession {
58
+ #sessionID = genSessionID('qs');
59
+
60
+ /** Parent client */
61
+ #client = client;
62
+
63
+ /** @type {SymbolListeners} */
64
+ #symbolListeners = {};
65
+
66
+ /**
67
+ * @typedef {Object} quoteSessionOptions Quote Session options
68
+ * @prop {'all' | 'price'} [fields] Asked quote fields
69
+ * @prop {quoteField[]} [customFields] List of asked quote fields
70
+ */
71
+
72
+ /**
73
+ * @param {quoteSessionOptions} options Quote settings options
74
+ */
75
+ constructor(options = {}) {
76
+ this.#client.sessions[this.#sessionID] = {
77
+ type: 'quote',
78
+ onData: (packet) => {
79
+ if (global.TW_DEBUG) console.log('§90§30§102 QUOTE SESSION §0 DATA', packet);
80
+
81
+ if (packet.type === 'quote_completed') {
82
+ const symbol = packet.data[1];
83
+ if (!this.#symbolListeners[symbol]) {
84
+ this.#client.send('quote_remove_symbols', [this.#sessionID, symbol]);
85
+ return;
86
+ }
87
+ this.#symbolListeners[symbol].forEach((h) => h(packet));
88
+ }
89
+
90
+ if (packet.type === 'qsd') {
91
+ const symbol = packet.data[1].n;
92
+ if (!this.#symbolListeners[symbol]) {
93
+ this.#client.send('quote_remove_symbols', [this.#sessionID, symbol]);
94
+ return;
95
+ }
96
+ this.#symbolListeners[symbol].forEach((h) => h(packet));
97
+ }
98
+ },
99
+ };
100
+
101
+ const fields = (options.customFields && options.customFields.length > 0
102
+ ? options.customFields
103
+ : getQuoteFields(options.fields)
104
+ );
105
+
106
+ this.#client.send('quote_create_session', [this.#sessionID]);
107
+ this.#client.send('quote_set_fields', [this.#sessionID, ...fields]);
108
+ }
109
+
110
+ /** @type {QuoteSessionBridge} */
111
+ #quoteSession = {
112
+ sessionID: this.#sessionID,
113
+ symbolListeners: this.#symbolListeners,
114
+ send: (t, p) => this.#client.send(t, p),
115
+ };
116
+
117
+ /** @constructor */
118
+ Market = quoteMarketConstructor(this.#quoteSession);
119
+
120
+ /** Delete the quote session */
121
+ delete() {
122
+ this.#client.send('quote_delete_session', [this.#sessionID]);
123
+ }
124
+ };
package/src/types.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @typedef {string} MarketSymbol Market symbol (like: 'BTCEUR' or 'KRAKEN:BTCEUR')
3
+ *
4
+ * @typedef {'Etc/UTC' | 'exchange'
5
+ * | 'Pacific/Honolulu' | 'America/Juneau' | 'America/Los_Angeles'
6
+ * | 'America/Phoenix' | 'America/Vancouver' | 'US/Mountain'
7
+ * | 'America/El_Salvador' | 'America/Bogota' | 'America/Chicago'
8
+ * | 'America/Lima' | 'America/Mexico_City' | 'America/Caracas'
9
+ * | 'America/New_York' | 'America/Toronto' | 'America/Argentina/Buenos_Aires'
10
+ * | 'America/Santiago' | 'America/Sao_Paulo' | 'Atlantic/Reykjavik'
11
+ * | 'Europe/Dublin' | 'Africa/Lagos' | 'Europe/Lisbon' | 'Europe/London'
12
+ * | 'Europe/Amsterdam' | 'Europe/Belgrade' | 'Europe/Berlin'
13
+ * | 'Europe/Brussels' | 'Europe/Copenhagen' | 'Africa/Johannesburg'
14
+ * | 'Africa/Cairo' | 'Europe/Luxembourg' | 'Europe/Madrid' | 'Europe/Malta'
15
+ * | 'Europe/Oslo' | 'Europe/Paris' | 'Europe/Rome' | 'Europe/Stockholm'
16
+ * | 'Europe/Warsaw' | 'Europe/Zurich' | 'Europe/Athens' | 'Asia/Bahrain'
17
+ * | 'Europe/Helsinki' | 'Europe/Istanbul' | 'Asia/Jerusalem' | 'Asia/Kuwait'
18
+ * | 'Europe/Moscow' | 'Asia/Qatar' | 'Europe/Riga' | 'Asia/Riyadh'
19
+ * | 'Europe/Tallinn' | 'Europe/Vilnius' | 'Asia/Tehran' | 'Asia/Dubai'
20
+ * | 'Asia/Muscat' | 'Asia/Ashkhabad' | 'Asia/Kolkata' | 'Asia/Almaty'
21
+ * | 'Asia/Bangkok' | 'Asia/Jakarta' | 'Asia/Ho_Chi_Minh' | 'Asia/Chongqing'
22
+ * | 'Asia/Hong_Kong' | 'Australia/Perth' | 'Asia/Shanghai' | 'Asia/Singapore'
23
+ * | 'Asia/Taipei' | 'Asia/Seoul' | 'Asia/Tokyo' | 'Australia/Brisbane'
24
+ * | 'Australia/Adelaide' | 'Australia/Sydney' | 'Pacific/Norfolk'
25
+ * | 'Pacific/Auckland' | 'Pacific/Fakaofo' | 'Pacific/Chatham'} Timezone (Chart) timezone
26
+ *
27
+ * @typedef {'1' | '3' | '5' | '15' | '30'
28
+ * | '45' | '60' | '120' | '180' | '240'
29
+ * | '1D' | '1W' | '1M' | 'D' | 'W' | 'M'} TimeFrame
30
+ */
31
+
32
+ module.exports = {};
package/src/utils.js ADDED
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ genSessionID(type = 'xs') {
3
+ let r = '';
4
+ const c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
5
+ for (let i = 0; i < 12; i += 1) r += c.charAt(Math.floor(Math.random() * c.length));
6
+ return `${type}_${r}`;
7
+ },
8
+ };
package/test.js ADDED
@@ -0,0 +1,155 @@
1
+ require('@mathieuc/console')(
2
+ '§', // Character you want to use (defaut: '§')
3
+ true, // Active timestamp (defaut: false)
4
+ );
5
+
6
+ const TradingView = require('./main');
7
+
8
+ const log = (...msg) => console.log('§90§30§103 TEST §0', ...msg);
9
+
10
+ const client = new TradingView.Client({ DEBUG: true });
11
+
12
+ client.onEvent((event, data) => {
13
+ log('EVENT:', event, data);
14
+ });
15
+
16
+ client.onError((...error) => {
17
+ log(...error);
18
+ });
19
+
20
+ // const quoteSession = new client.Session.Quote({
21
+ // fields: 'price',
22
+ // });
23
+
24
+ // const BTC = new quoteSession.Market('BTCEUR');
25
+
26
+ // BTC.onLoaded(() => {
27
+ // log('BTCEUR LOADED !');
28
+ // });
29
+
30
+ // BTC.onData((data) => {
31
+ // log('BTCEUR DATA:', data);
32
+ // });
33
+
34
+ // BTC.onError((...err) => {
35
+ // log('BTCEUR ERROR:', err);
36
+ // });
37
+
38
+ const chart = new client.Session.Chart();
39
+ chart.setMarket('COINBASE:BTCEUR');
40
+ chart.setSeries('60');
41
+
42
+ chart.onSymbolLoaded(() => {
43
+ log('Market loaded:', chart.infos.full_name);
44
+ });
45
+
46
+ chart.onError((...err) => {
47
+ log('CHART ERROR:', ...err);
48
+ });
49
+
50
+ chart.onUpdate(() => {
51
+ const last = chart.periods[0];
52
+ if (!last) return;
53
+ log(`Market last period: ${last.close}`);
54
+ });
55
+
56
+ TradingView.getIndicator('STD;Supertrend%Strategy').then((indicator) => {
57
+ indicator.setOption('commission_type', 'percent');
58
+ indicator.setOption('commission_value', 0);
59
+ indicator.setOption('initial_capital', 25000);
60
+ indicator.setOption('default_qty_value', 20);
61
+ indicator.setOption('default_qty_type', 'percent_of_equity');
62
+ indicator.setOption('currency', 'EUR');
63
+ indicator.setOption('pyramiding', 10);
64
+
65
+ const SuperTrend = new chart.Study(indicator);
66
+
67
+ SuperTrend.onReady(() => {
68
+ log('SuperTrend ready !');
69
+ });
70
+
71
+ SuperTrend.onError((...err) => {
72
+ log('SuperTrend ERROR:', err[0]);
73
+ });
74
+
75
+ // let QTY = 10;
76
+ // setInterval(() => {
77
+ // QTY += 10;
78
+ // console.log('TRY WITH', QTY, '%');
79
+ // indicator.setOption('default_qty_value', QTY);
80
+
81
+ // SuperTrend.setIndicator(indicator);
82
+ // }, 5000);
83
+
84
+ SuperTrend.onUpdate((changes) => {
85
+ // MarketCipher B is a strategy so it sends a strategy report
86
+ log('SuperTrend update:', changes);
87
+
88
+ const perfReport = SuperTrend.strategyReport.performance;
89
+
90
+ log('Performance report:', {
91
+ total: {
92
+ trades: perfReport.all.totalTrades,
93
+ perf: `${Math.round(perfReport.all.netProfitPercent * 10000) / 100} %`,
94
+ profit: `${Math.round(perfReport.all.netProfit * 1000) / 1000} €`,
95
+ },
96
+ buy: {
97
+ trades: perfReport.long.totalTrades,
98
+ perf: `${Math.round(perfReport.long.netProfitPercent * 10000) / 100} %`,
99
+ profit: `${Math.round(perfReport.long.netProfit * 1000) / 1000} €`,
100
+ },
101
+ sell: {
102
+ trades: perfReport.short.totalTrades,
103
+ perf: `${Math.round(perfReport.short.netProfitPercent * 10000) / 100} %`,
104
+ profit: `${Math.round(perfReport.short.netProfit * 1000) / 1000} €`,
105
+ },
106
+ });
107
+
108
+ if (changes.includes('fullReport')) {
109
+ log('Last trade:', SuperTrend.strategyReport.trades[0]);
110
+ // Do something...
111
+
112
+ // // Remove SuperTrend strategy from the chart
113
+ // SuperTrend.remove();
114
+ }
115
+ });
116
+ });
117
+
118
+ TradingView.getIndicator('PUB;uA35GeckoTA2EfgI63SD2WCSmca4njxp').then((indicator) => {
119
+ indicator.setOption('Show_WT_Hidden_Divergences', true);
120
+ indicator.setOption('Show_Stoch_Regular_Divergences', true);
121
+ indicator.setOption('Show_Stoch_Hidden_Divergences', true);
122
+
123
+ const CipherB = new chart.Study(indicator);
124
+
125
+ CipherB.onReady(() => {
126
+ log('MarketCipher B ready !');
127
+ });
128
+
129
+ CipherB.onError((...err) => {
130
+ log('MarketCipher B ERROR:', err[0]);
131
+ });
132
+
133
+ CipherB.onUpdate(() => {
134
+ const last = CipherB.periods[0];
135
+ // MarketCipher B is not a strategy so it only sends plots values
136
+ log('MarketCipher B last values:', {
137
+ VWAP: Math.round(last.VWAP * 1000) / 1000,
138
+ moneyFlow: (last.RSIMFIArea >= 0) ? 'POSITIVE' : 'NEGATIVE',
139
+ buyCircle: last.Buy_and_sell_circle && last.VWAP > 0,
140
+ sellCircle: last.Buy_and_sell_circle && last.VWAP < 0,
141
+ });
142
+
143
+ // Do something...
144
+ });
145
+ });
146
+
147
+ // setTimeout(() => {
148
+ // log('Set timeframe to 240...');
149
+ // chart.setSeries('240');
150
+ // }, 5000);
151
+
152
+ // setInterval(() => {
153
+ // log('Fetch 100 more periods...');
154
+ // chart.fetchMore(100);
155
+ // }, 10000);
@@ -1,76 +1,76 @@
1
- const https = require('https');
2
-
3
- /**
4
- * @typedef {Object} WebhookMessage Represents a Discord webhook message
5
- * @property {string} content
6
- * @property {Embed[]} [embeds]
7
- */
8
-
9
- /**
10
- * @typedef {Object} Embed Discord Embed
11
- * @property {string} [title]
12
- * @property {string} [description]
13
- * @property {number} [color]
14
- * @property {string} [url]
15
- * @property {string} [timestamp]
16
-
17
- * @property {Object} [author]
18
- * @property {string} [author.name]
19
- * @property {string} [author.icon_url]
20
- * @property {string} [author.url]
21
-
22
- * @property {Object} [thumbnail]
23
- * @property {string} [thumbnail.url]
24
-
25
- * @property {Object} [footer]
26
- * @property {string} [footer.text]
27
- * @property {string} [footer.icon_url]
28
- */
29
-
30
- /**
31
- * @typedef {Object} SendResult
32
- * @property {boolean} success
33
- * @property {Object} [result] If error
34
- * @property {string[]} [result.webhook_id]
35
- */
36
-
37
- /**
38
- * Create an instance of Discord Webhook API
39
- * @param {string} token Discord Webhook token (after .../api/webhooks/)
40
- */
41
-
42
- module.exports = function createInstance(token = '') {
43
- /**
44
- * Send a message
45
- * @name MessageSender
46
- * @param {WebhookMessage} message
47
- * @returns {Promise<SendResult>}
48
- */
49
- return (message) => new Promise((cb, err) => {
50
- const req = https.request({
51
- method: 'POST',
52
- hostname: 'discordapp.com',
53
- path: `/api/webhooks/${token}`,
54
- headers: {
55
- 'Content-Type': 'application/json',
56
- },
57
- maxRedirects: 20,
58
- }, (res) => {
59
- let data = '';
60
- res.on('data', (d) => { data += d; });
61
- res.on('end', () => {
62
- if (!data) cb({ success: true });
63
- else {
64
- cb({
65
- success: false,
66
- result: JSON.parse(data),
67
- });
68
- }
69
- });
70
- });
71
-
72
- req.on('error', err);
73
- req.write(JSON.stringify(message));
74
- req.end();
75
- });
76
- };
1
+ const https = require('https');
2
+
3
+ /**
4
+ * @typedef {Object} WebhookMessage Represents a Discord webhook message
5
+ * @property {string} content
6
+ * @property {Embed[]} [embeds]
7
+ */
8
+
9
+ /**
10
+ * @typedef {Object} Embed Discord Embed
11
+ * @property {string} [title]
12
+ * @property {string} [description]
13
+ * @property {number} [color]
14
+ * @property {string} [url]
15
+ * @property {string} [timestamp]
16
+
17
+ * @property {Object} [author]
18
+ * @property {string} [author.name]
19
+ * @property {string} [author.icon_url]
20
+ * @property {string} [author.url]
21
+
22
+ * @property {Object} [thumbnail]
23
+ * @property {string} [thumbnail.url]
24
+
25
+ * @property {Object} [footer]
26
+ * @property {string} [footer.text]
27
+ * @property {string} [footer.icon_url]
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} SendResult
32
+ * @property {boolean} success
33
+ * @property {Object} [result] If error
34
+ * @property {string[]} [result.webhook_id]
35
+ */
36
+
37
+ /**
38
+ * Create an instance of Discord Webhook API
39
+ * @param {string} token Discord Webhook token (after .../api/webhooks/)
40
+ */
41
+
42
+ module.exports = function createInstance(token = '') {
43
+ /**
44
+ * Send a message
45
+ * @name MessageSender
46
+ * @param {WebhookMessage} message
47
+ * @returns {Promise<SendResult>}
48
+ */
49
+ return (message) => new Promise((cb, err) => {
50
+ const req = https.request({
51
+ method: 'POST',
52
+ hostname: 'discordapp.com',
53
+ path: `/api/webhooks/${token}`,
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ },
57
+ maxRedirects: 20,
58
+ }, (res) => {
59
+ let data = '';
60
+ res.on('data', (d) => { data += d; });
61
+ res.on('end', () => {
62
+ if (!data) cb({ success: true });
63
+ else {
64
+ cb({
65
+ success: false,
66
+ result: JSON.parse(data),
67
+ });
68
+ }
69
+ });
70
+ });
71
+
72
+ req.on('error', err);
73
+ req.write(JSON.stringify(message));
74
+ req.end();
75
+ });
76
+ };
@@ -1,11 +1,11 @@
1
- const fs = require('fs');
2
-
3
- if (fs.existsSync('.env')) {
4
- const envFile = fs.readFileSync('.env', 'utf8');
5
- envFile.split('\n') // LF
6
- .forEach((l) => {
7
- const s = l.split('=');
8
- // eslint-disable-next-line
9
- if (s[1]) process.env[s[0]] = s[1];
10
- });
11
- }
1
+ const fs = require('fs');
2
+
3
+ if (fs.existsSync('.env')) {
4
+ const envFile = fs.readFileSync('.env', 'utf8');
5
+ envFile.split('\n') // LF
6
+ .forEach((l) => {
7
+ const s = l.split('=');
8
+ // eslint-disable-next-line
9
+ if (s[1]) process.env[s[0]] = s[1];
10
+ });
11
+ }