@mathieuc/tradingview 2.0.5 → 3.0.0-pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +18 -16
- package/.github/FUNDING.yml +1 -0
- package/README.md +26 -96
- package/examples/AllPrivateIndicators.js +35 -0
- package/examples/BuiltInIndicator.js +41 -0
- package/examples/CustomChartType.js +121 -0
- package/examples/CustomTimeframe.js +29 -0
- package/examples/Errors.js +153 -0
- package/examples/GraphicIndicator.js +44 -0
- package/examples/MultipleSyncFetch.js +38 -0
- package/examples/Search.js +15 -0
- package/examples/SimpleChart.js +63 -0
- package/examples/_RenkoIndicator.js +41 -0
- package/main.js +9 -337
- package/package.json +10 -2
- package/src/chart/graphicParser.js +110 -0
- package/src/chart/session.js +367 -0
- package/src/chart/study.js +403 -0
- package/src/classes/BuiltInIndicator.js +119 -0
- package/src/classes/PineIndicator.js +132 -0
- package/src/client.js +280 -0
- package/src/miscRequests.js +525 -0
- package/src/protocol.js +54 -0
- package/src/quote/market.js +123 -0
- package/src/quote/session.js +124 -0
- package/src/types.js +32 -0
- package/src/utils.js +8 -0
- package/test.js +155 -0
- package/tests/alerts/discordWH.js +76 -76
- package/tests/alerts/drawings.js +41 -0
- package/tests/alerts/envLoader.js +11 -11
- package/tests/alerts/manualAlert.js +77 -77
- package/tests/alerts/sentNotifs.js +16 -16
- package/tests/alerts/strategyAlert.js +94 -94
- package/tests/analysis.js +3 -3
- package/tests/indicator.js +42 -42
- package/tests/strategy.js +56 -0
- package/miscRequests.js +0 -285
|
@@ -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
|
+
};
|
|
@@ -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,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
|
+
}
|