@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.
@@ -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,41 +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
+ 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,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
+ }