@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,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
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
|
+
};
|
package/tests/alerts/drawings.js
CHANGED
|
@@ -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
|
+
}
|