@mathieuc/tradingview 3.2.0 → 3.3.2
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 +6 -0
- package/README.md +16 -3
- package/docs/DOCS.md +3 -0
- package/examples/BuiltInIndicator.js +1 -1
- package/examples/GetDrawings.js +26 -0
- package/examples/UserLogin.js +16 -0
- package/package.json +2 -3
- package/src/FormData.js +1 -0
- package/src/chart/session.js +4 -2
- package/src/chart/study.js +83 -69
- package/src/classes/BuiltInIndicator.js +1 -0
- package/src/classes/PineIndicator.js +7 -7
- package/src/classes/PinePermManager.js +5 -4
- package/src/client.js +16 -9
- package/src/miscRequests.js +143 -70
- package/src/protocol.js +7 -1
- package/src/request.js +37 -37
- package/src/types.js +6 -2
- package/src/utils.js +6 -0
- package/test.js +2 -0
- package/tests/4. Indicators.js +4 -3
- package/examples/CurrentTimeframe.js +0 -34
- package/examples/_RenkoIndicator.js +0 -41
- package/tests/disabled/x. Template.js +0 -24
package/.eslintrc.js
CHANGED
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
# TradingView API [](https://GitHub.com/Mathieu2301/TradingView-API/stargazers/)
|
|
2
2
|
|
|
3
3
|
[](https://github.com/Mathieu2301/TradingView-API/actions/workflows/node.js.yml)
|
|
4
|
-
[](https://npmjs.com/@mathieuc/tradingview)
|
|
4
|
+
[](https://www.codefactor.io/repository/github/mathieu2301/tradingview-api/overview/main)
|
|
6
5
|
[](https://GitHub.com/Mathieu2301/TradingView-API/commit/)
|
|
6
|
+
[](https://npmjs.com/@mathieuc/tradingview)
|
|
7
|
+
[](https://npmjs.com/@mathieuc/tradingview)
|
|
7
8
|
[](https://npmjs.com/package/@mathieuc/tradingview)
|
|
8
9
|
|
|
9
10
|
Get realtime market prices and indicator values from Tradingview !
|
|
10
11
|
|
|
11
12
|
## Features
|
|
13
|
+
|
|
12
14
|
- [x] Premium features
|
|
13
15
|
- [x] Automatically backtest many strategies and try many settings in a very little time
|
|
14
16
|
- [x] Get drawings you made on your chart
|
|
@@ -26,6 +28,7 @@ Get realtime market prices and indicator values from Tradingview !
|
|
|
26
28
|
- IF YOU WANT A FEATURE, ASK ME !
|
|
27
29
|
|
|
28
30
|
## Possibilities
|
|
31
|
+
|
|
29
32
|
- Trading bot
|
|
30
33
|
- Discord alerts
|
|
31
34
|
- Hard backtest
|
|
@@ -33,21 +36,31 @@ Get realtime market prices and indicator values from Tradingview !
|
|
|
33
36
|
- Free replay mode for all timeframes
|
|
34
37
|
|
|
35
38
|
___
|
|
39
|
+
|
|
36
40
|
## Installation
|
|
37
41
|
|
|
38
42
|
Stable version:
|
|
43
|
+
|
|
39
44
|
```ruby
|
|
40
45
|
npm i @mathieuc/tradingview
|
|
41
46
|
```
|
|
47
|
+
|
|
42
48
|
Last version:
|
|
49
|
+
|
|
43
50
|
```ruby
|
|
44
51
|
npm i github:Mathieu2301/TradingView-API
|
|
45
52
|
```
|
|
46
53
|
|
|
47
54
|
## Examples
|
|
55
|
+
|
|
48
56
|
You can find all the examples and snippets in `./examples` folder.
|
|
49
57
|
|
|
58
|
+
## Before opening an issue
|
|
59
|
+
|
|
60
|
+
Please look at examples and previously resolved issues before opening a new one. I can't help everyone (especially for questions that are not library related but JavaScript related). Thank you for your understanding.
|
|
50
61
|
___
|
|
62
|
+
|
|
51
63
|
## Problems
|
|
64
|
+
|
|
52
65
|
If you have errors in console or unwanted behavior,
|
|
53
66
|
please create an issue [here](https://github.com/Mathieu2301/Tradingview-API/issues).
|
package/docs/DOCS.md
ADDED
|
@@ -7,7 +7,7 @@ const TradingView = require('../main');
|
|
|
7
7
|
|
|
8
8
|
const volumeProfile = new TradingView.BuiltInIndicator('VbPFixed@tv-basicstudies-139!');
|
|
9
9
|
|
|
10
|
-
if (!process.argv[2] &&
|
|
10
|
+
if (!process.argv[2] && !['VbPFixed@tv-basicstudies-139!', 'Volume@tv-basicstudies-144'].includes(volumeProfile.type)) {
|
|
11
11
|
throw Error('Please specify your \'sessionid\' cookie');
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const TradingView = require('../main');
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
This example tests the
|
|
5
|
+
getDrawings function
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// First parameter must be the layoutID
|
|
9
|
+
// (if the layout is private) Second parameter must be the sessionid cookie
|
|
10
|
+
// (if the layout is private) Third parameter must be the userid (you can use getUser function)
|
|
11
|
+
|
|
12
|
+
if (!process.argv[2]) throw Error('Please specify a layoutID');
|
|
13
|
+
|
|
14
|
+
TradingView.getDrawings(process.argv[2], null, {
|
|
15
|
+
session: process.argv[3],
|
|
16
|
+
id: process.argv[4],
|
|
17
|
+
}).then((drawings) => {
|
|
18
|
+
console.log(`Found ${drawings.length} drawings:`, drawings.map((d) => ({
|
|
19
|
+
id: d.id,
|
|
20
|
+
symbol: d.symbol,
|
|
21
|
+
type: d.type,
|
|
22
|
+
text: d.state.text,
|
|
23
|
+
})));
|
|
24
|
+
}).catch((err) => {
|
|
25
|
+
console.error('Error:', err.message);
|
|
26
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const TradingView = require('../main');
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
This example tests the
|
|
5
|
+
user login function
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
if (!process.argv[2]) throw Error('Please specify your username/email');
|
|
9
|
+
if (!process.argv[3]) throw Error('Please specify your password');
|
|
10
|
+
|
|
11
|
+
TradingView.loginUser(process.argv[2], process.argv[3], false).then((user) => {
|
|
12
|
+
console.log('User:', user);
|
|
13
|
+
console.log('Sessionid:', user.session);
|
|
14
|
+
}).catch((err) => {
|
|
15
|
+
console.error('Login error:', err.message);
|
|
16
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mathieuc/tradingview",
|
|
3
|
-
"version": "3.2
|
|
3
|
+
"version": "3.3.2",
|
|
4
4
|
"description": "Tradingview instant stocks API, indicator alerts, trading bot, and more !",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"@mathieuc/console": "^1.0.1",
|
|
35
35
|
"eslint": "^7.25.0",
|
|
36
36
|
"eslint-config-airbnb-base": "^14.2.1",
|
|
37
|
-
"eslint-plugin-import": "^2.22.1"
|
|
38
|
-
"jsdoc-to-markdown": "^7.1.0"
|
|
37
|
+
"eslint-plugin-import": "^2.22.1"
|
|
39
38
|
}
|
|
40
39
|
}
|
package/src/FormData.js
CHANGED
package/src/chart/session.js
CHANGED
|
@@ -64,7 +64,9 @@ const ChartTypes = {
|
|
|
64
64
|
* @prop {string} session Session (ex: '24x7')
|
|
65
65
|
* @prop {string} session-correction Session correction
|
|
66
66
|
* @prop {string} session-display Session display (ex: '24x7')
|
|
67
|
-
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/**
|
|
68
70
|
* @typedef {Object} MarketInfos
|
|
69
71
|
* @prop {string} series_id Used series (ex: 'ser_1')
|
|
70
72
|
* @prop {string} base_currency Base currency (ex: 'BTC')
|
|
@@ -120,7 +122,7 @@ module.exports = (client) => class ChartSession {
|
|
|
120
122
|
#replayMode = false;
|
|
121
123
|
|
|
122
124
|
/** @type {Object<string, (): any>} */
|
|
123
|
-
#replayOKCB = {}
|
|
125
|
+
#replayOKCB = {};
|
|
124
126
|
|
|
125
127
|
/** Parent client */
|
|
126
128
|
#client = client;
|
package/src/chart/study.js
CHANGED
|
@@ -16,11 +16,11 @@ function getInputs(options) {
|
|
|
16
16
|
if (options.pineId) pineInputs.pineId = options.pineId;
|
|
17
17
|
if (options.pineVersion) pineInputs.pineVersion = options.pineVersion;
|
|
18
18
|
|
|
19
|
-
Object.keys(options.inputs).forEach((inputID) => {
|
|
19
|
+
Object.keys(options.inputs).forEach((inputID, n) => {
|
|
20
20
|
const input = options.inputs[inputID];
|
|
21
21
|
|
|
22
22
|
pineInputs[inputID] = {
|
|
23
|
-
v: input.value,
|
|
23
|
+
v: (input.type !== 'color') ? input.value : n,
|
|
24
24
|
f: input.isFake,
|
|
25
25
|
t: input.type,
|
|
26
26
|
};
|
|
@@ -35,80 +35,80 @@ function getInputs(options) {
|
|
|
35
35
|
/**
|
|
36
36
|
* @typedef {Object} TradeReport Trade report
|
|
37
37
|
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
40
|
-
* @
|
|
41
|
-
* @
|
|
42
|
-
* @
|
|
43
|
-
|
|
44
|
-
* @
|
|
45
|
-
* @
|
|
46
|
-
* @
|
|
47
|
-
* @
|
|
48
|
-
|
|
49
|
-
* @
|
|
50
|
-
* @
|
|
51
|
-
* @
|
|
52
|
-
* @
|
|
53
|
-
* @
|
|
38
|
+
* @prop {Object} entry Trade entry
|
|
39
|
+
* @prop {string} entry.name Trade name
|
|
40
|
+
* @prop {'long' | 'short'} entry.type Entry type (long/short)
|
|
41
|
+
* @prop {number} entry.value Entry price value
|
|
42
|
+
* @prop {number} entry.time Entry timestamp
|
|
43
|
+
|
|
44
|
+
* @prop {Object} exit Trade exit
|
|
45
|
+
* @prop {'' | string} exit.name Trade name ('' if false exit)
|
|
46
|
+
* @prop {number} exit.value Exit price value
|
|
47
|
+
* @prop {number} exit.time Exit timestamp
|
|
48
|
+
|
|
49
|
+
* @prop {number} quantity Trade quantity
|
|
50
|
+
* @prop {RelAbsValue} profit Trade profit
|
|
51
|
+
* @prop {RelAbsValue} cumulative Trade cummulative profit
|
|
52
|
+
* @prop {RelAbsValue} runup Trade run-up
|
|
53
|
+
* @prop {RelAbsValue} drawdown Trade drawdown
|
|
54
54
|
*/
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* @typedef {Object} PerfReport
|
|
58
|
-
* @
|
|
59
|
-
* @
|
|
60
|
-
* @
|
|
61
|
-
* @
|
|
62
|
-
* @
|
|
63
|
-
* @
|
|
64
|
-
* @
|
|
65
|
-
* @
|
|
66
|
-
* @
|
|
67
|
-
* @
|
|
68
|
-
* @
|
|
69
|
-
* @
|
|
70
|
-
* @
|
|
71
|
-
* @
|
|
72
|
-
* @
|
|
73
|
-
* @
|
|
74
|
-
* @
|
|
75
|
-
* @
|
|
76
|
-
* @
|
|
77
|
-
* @
|
|
78
|
-
* @
|
|
79
|
-
* @
|
|
80
|
-
* @
|
|
81
|
-
* @
|
|
82
|
-
* @
|
|
83
|
-
* @
|
|
84
|
-
* @
|
|
85
|
-
* @
|
|
86
|
-
* @
|
|
58
|
+
* @prop {number} avgBarsInTrade Average bars in trade
|
|
59
|
+
* @prop {number} avgBarsInWinTrade Average bars in winning trade
|
|
60
|
+
* @prop {number} avgBarsInLossTrade Average bars in losing trade
|
|
61
|
+
* @prop {number} avgTrade Average trade gain
|
|
62
|
+
* @prop {number} avgTradePercent Average trade performace
|
|
63
|
+
* @prop {number} avgLosTrade Average losing trade gain
|
|
64
|
+
* @prop {number} avgLosTradePercent Average losing trade performace
|
|
65
|
+
* @prop {number} avgWinTrade Average winning trade gain
|
|
66
|
+
* @prop {number} avgWinTradePercent Average winning trade performace
|
|
67
|
+
* @prop {number} commissionPaid Commission paid
|
|
68
|
+
* @prop {number} grossLoss Gross loss value
|
|
69
|
+
* @prop {number} grossLossPercent Gross loss percent
|
|
70
|
+
* @prop {number} grossProfit Gross profit
|
|
71
|
+
* @prop {number} grossProfitPercent Gross profit percent
|
|
72
|
+
* @prop {number} largestLosTrade Largest losing trade gain
|
|
73
|
+
* @prop {number} largestLosTradePercent Largent losing trade performance (percentage)
|
|
74
|
+
* @prop {number} largestWinTrade Largest winning trade gain
|
|
75
|
+
* @prop {number} largestWinTradePercent Largest winning trade performance (percentage)
|
|
76
|
+
* @prop {number} marginCalls Margin calls
|
|
77
|
+
* @prop {number} maxContractsHeld Max Contracts Held
|
|
78
|
+
* @prop {number} netProfit Net profit
|
|
79
|
+
* @prop {number} netProfitPercent Net performance (percentage)
|
|
80
|
+
* @prop {number} numberOfLosingTrades Number of losing trades
|
|
81
|
+
* @prop {number} numberOfWiningTrades Number of winning trades
|
|
82
|
+
* @prop {number} percentProfitable Strategy winrate
|
|
83
|
+
* @prop {number} profitFactor Profit factor
|
|
84
|
+
* @prop {number} ratioAvgWinAvgLoss Ratio Average Win / Average Loss
|
|
85
|
+
* @prop {number} totalOpenTrades Total open trades
|
|
86
|
+
* @prop {number} totalTrades Total trades
|
|
87
87
|
*/
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* @typedef {Object} StrategyReport
|
|
91
|
-
* @
|
|
92
|
-
* @
|
|
93
|
-
* @
|
|
94
|
-
* @
|
|
95
|
-
* @
|
|
96
|
-
* @
|
|
97
|
-
* @
|
|
98
|
-
* @
|
|
99
|
-
* @
|
|
100
|
-
* @
|
|
101
|
-
* @
|
|
102
|
-
* @
|
|
103
|
-
* @
|
|
104
|
-
* @
|
|
105
|
-
* @
|
|
106
|
-
* @
|
|
107
|
-
* @
|
|
108
|
-
* @
|
|
109
|
-
* @
|
|
110
|
-
* @
|
|
111
|
-
* @
|
|
91
|
+
* @prop {'EUR' | 'USD' | 'JPY' | '' | 'CHF'} [currency] Selected currency
|
|
92
|
+
* @prop {TradeReport[]} trades Trade list starting by the last
|
|
93
|
+
* @prop {Object} history History Chart value
|
|
94
|
+
* @prop {number[]} [history.buyHold] Buy hold values
|
|
95
|
+
* @prop {number[]} [history.buyHoldPercent] Buy hold percent values
|
|
96
|
+
* @prop {number[]} [history.drawDown] Drawdown values
|
|
97
|
+
* @prop {number[]} [history.drawDownPercent] Drawdown percent values
|
|
98
|
+
* @prop {number[]} [history.equity] Equity values
|
|
99
|
+
* @prop {number[]} [history.equityPercent] Equity percent values
|
|
100
|
+
* @prop {Object} performance Strategy performance
|
|
101
|
+
* @prop {PerfReport} [performance.all] Strategy long/short performances
|
|
102
|
+
* @prop {PerfReport} [performance.long] Strategy long performances
|
|
103
|
+
* @prop {PerfReport} [performance.short] Strategy short performances
|
|
104
|
+
* @prop {number} [performance.buyHoldReturn] Strategy Buy & Hold Return
|
|
105
|
+
* @prop {number} [performance.buyHoldReturnPercent] Strategy Buy & Hold Return percent
|
|
106
|
+
* @prop {number} [performance.maxDrawDown] Strategy max drawdown
|
|
107
|
+
* @prop {number} [performance.maxDrawDownPercent] Strategy max drawdown percent
|
|
108
|
+
* @prop {number} [performance.openPL] Strategy Open P&L (Profit And Loss)
|
|
109
|
+
* @prop {number} [performance.openPLPercent] Strategy Open P&L (Profit And Loss) percent
|
|
110
|
+
* @prop {number} [performance.sharpeRatio] Strategy Sharpe Ratio
|
|
111
|
+
* @prop {number} [performance.sortinoRatio] Strategy Sortino Ratio
|
|
112
112
|
*/
|
|
113
113
|
|
|
114
114
|
/**
|
|
@@ -280,6 +280,16 @@ module.exports = (chartSession) => class ChartStudy {
|
|
|
280
280
|
changes.push('perfReport');
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
if (parsed.data && parsed.data.report && parsed.data.report.trades) {
|
|
284
|
+
this.#strategyReport.trades = parsed.data.report.trades;
|
|
285
|
+
changes.push('tradesReport');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (parsed.data && parsed.data.report && parsed.data.report.history) {
|
|
289
|
+
this.#strategyReport.history = parsed.data.report.history;
|
|
290
|
+
changes.push('historyReport');
|
|
291
|
+
}
|
|
292
|
+
|
|
283
293
|
if (parsed.dataCompressed) {
|
|
284
294
|
const parsedC = await parseCompressed(parsed.dataCompressed);
|
|
285
295
|
|
|
@@ -372,7 +382,11 @@ module.exports = (chartSession) => class ChartStudy {
|
|
|
372
382
|
this.#callbacks.studyCompleted.push(cb);
|
|
373
383
|
}
|
|
374
384
|
|
|
375
|
-
/**
|
|
385
|
+
/**
|
|
386
|
+
* @typedef {
|
|
387
|
+
* 'plots' | 'perfReport' | 'tradesReport' | 'historyReport' | 'fullReport'
|
|
388
|
+
* } UpdateChangeType
|
|
389
|
+
*/
|
|
376
390
|
|
|
377
391
|
/**
|
|
378
392
|
* When an indicator update happens
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* @property {string} inline Input inline name
|
|
5
5
|
* @property {string} [internalID] Input internal ID
|
|
6
6
|
* @property {string} [tooltip] Input tooltip
|
|
7
|
-
* @property {'text' | 'source' | 'integer'
|
|
7
|
+
* @property {'text' | 'source' | 'integer'
|
|
8
|
+
* | 'float' | 'resolution' | 'bool' | 'color'
|
|
9
|
+
* } type Input type
|
|
8
10
|
* @property {string | number | boolean} value Input default value
|
|
9
11
|
* @property {boolean} isHidden If the input is hidden
|
|
10
12
|
* @property {boolean} isFake If the input is fake
|
|
@@ -26,6 +28,8 @@
|
|
|
26
28
|
* @typedef {'Script@tv-scripting-101!'
|
|
27
29
|
* | 'StrategyScript@tv-scripting-101!'} IndicatorType Indicator type
|
|
28
30
|
*/
|
|
31
|
+
|
|
32
|
+
/** @class */
|
|
29
33
|
module.exports = class PineIndicator {
|
|
30
34
|
#options;
|
|
31
35
|
|
|
@@ -113,12 +117,8 @@ module.exports = class PineIndicator {
|
|
|
113
117
|
text: 'String',
|
|
114
118
|
};
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|| (input.type === 'integer' && typeof value !== 'number')
|
|
119
|
-
|| (input.type === 'float' && typeof value !== 'number')
|
|
120
|
-
|| (input.type === 'text' && typeof value !== 'string')
|
|
121
|
-
) {
|
|
120
|
+
// eslint-disable-next-line valid-typeof
|
|
121
|
+
if (types[input.type] && typeof value !== types[input.type].toLowerCase()) {
|
|
122
122
|
throw new Error(`Input '${input.name}' (${propI}) must be a ${types[input.type]} !`);
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -10,6 +10,7 @@ const FormData = require('../FormData');
|
|
|
10
10
|
* @prop {string} created Authorization creation date
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/** @class */
|
|
13
14
|
class PinePermManager {
|
|
14
15
|
sessionId;
|
|
15
16
|
|
|
@@ -39,7 +40,7 @@ class PinePermManager {
|
|
|
39
40
|
* @returns {AuthorizationUser[]}
|
|
40
41
|
*/
|
|
41
42
|
async getUsers(limit = 10, order = '-created') {
|
|
42
|
-
const data = await request({
|
|
43
|
+
const { data } = await request({
|
|
43
44
|
method: 'POST',
|
|
44
45
|
host: 'www.tradingview.com',
|
|
45
46
|
path: `/pine_perm/list_users/?limit=${limit}&order_by=${order}`,
|
|
@@ -69,7 +70,7 @@ class PinePermManager {
|
|
|
69
70
|
formData.append('expiration', expiration.toString());
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
const data = await request({
|
|
73
|
+
const { data } = await request({
|
|
73
74
|
method: 'POST',
|
|
74
75
|
host: 'www.tradingview.com',
|
|
75
76
|
path: '/pine_perm/add/',
|
|
@@ -98,7 +99,7 @@ class PinePermManager {
|
|
|
98
99
|
formData.append('expiration', expiration.toISOString());
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
const data = await request({
|
|
102
|
+
const { data } = await request({
|
|
102
103
|
method: 'POST',
|
|
103
104
|
host: 'www.tradingview.com',
|
|
104
105
|
path: '/pine_perm/modify_user_expiration/',
|
|
@@ -123,7 +124,7 @@ class PinePermManager {
|
|
|
123
124
|
formData.append('pine_id', this.pineId);
|
|
124
125
|
formData.append('username_recip', username);
|
|
125
126
|
|
|
126
|
-
const data = await request({
|
|
127
|
+
const { data } = await request({
|
|
127
128
|
method: 'POST',
|
|
128
129
|
host: 'www.tradingview.com',
|
|
129
130
|
path: '/pine_perm/remove/',
|
package/src/client.js
CHANGED
|
@@ -15,9 +15,10 @@ const chartSessionGenerator = require('./chart/session');
|
|
|
15
15
|
/** @typedef {Object<string, Session>} SessionList Session list */
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* @
|
|
18
|
+
* @callback SendPacket Send a custom packet
|
|
19
19
|
* @param {string} t Packet type
|
|
20
20
|
* @param {string[]} p Packet data
|
|
21
|
+
* @returns {void}
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -33,6 +34,7 @@ const chartSessionGenerator = require('./chart/session');
|
|
|
33
34
|
* } ClientEvent
|
|
34
35
|
*/
|
|
35
36
|
|
|
37
|
+
/** @class */
|
|
36
38
|
module.exports = class Client {
|
|
37
39
|
#ws;
|
|
38
40
|
|
|
@@ -79,7 +81,7 @@ module.exports = class Client {
|
|
|
79
81
|
/**
|
|
80
82
|
* When client is connected
|
|
81
83
|
* @param {() => void} cb Callback
|
|
82
|
-
* @event
|
|
84
|
+
* @event onConnected
|
|
83
85
|
*/
|
|
84
86
|
onConnected(cb) {
|
|
85
87
|
this.#callbacks.connected.push(cb);
|
|
@@ -88,7 +90,7 @@ module.exports = class Client {
|
|
|
88
90
|
/**
|
|
89
91
|
* When client is disconnected
|
|
90
92
|
* @param {() => void} cb Callback
|
|
91
|
-
* @event
|
|
93
|
+
* @event onDisconnected
|
|
92
94
|
*/
|
|
93
95
|
onDisconnected(cb) {
|
|
94
96
|
this.#callbacks.disconnected.push(cb);
|
|
@@ -105,10 +107,12 @@ module.exports = class Client {
|
|
|
105
107
|
* @prop {string} javastudies Javastudies
|
|
106
108
|
* @prop {number} auth_scheme_vsn Auth scheme type
|
|
107
109
|
* @prop {string} via Socket IP
|
|
110
|
+
*/
|
|
108
111
|
|
|
112
|
+
/**
|
|
109
113
|
* When client is logged
|
|
110
114
|
* @param {(SocketSession: SocketSession) => void} cb Callback
|
|
111
|
-
* @event
|
|
115
|
+
* @event onLogged
|
|
112
116
|
*/
|
|
113
117
|
onLogged(cb) {
|
|
114
118
|
this.#callbacks.logged.push(cb);
|
|
@@ -117,7 +121,7 @@ module.exports = class Client {
|
|
|
117
121
|
/**
|
|
118
122
|
* When server is pinging the client
|
|
119
123
|
* @param {(i: number) => void} cb Callback
|
|
120
|
-
* @event
|
|
124
|
+
* @event onPing
|
|
121
125
|
*/
|
|
122
126
|
onPing(cb) {
|
|
123
127
|
this.#callbacks.ping.push(cb);
|
|
@@ -126,7 +130,7 @@ module.exports = class Client {
|
|
|
126
130
|
/**
|
|
127
131
|
* When unparsed data is received
|
|
128
132
|
* @param {(...{}) => void} cb Callback
|
|
129
|
-
* @event
|
|
133
|
+
* @event onData
|
|
130
134
|
*/
|
|
131
135
|
onData(cb) {
|
|
132
136
|
this.#callbacks.data.push(cb);
|
|
@@ -135,7 +139,7 @@ module.exports = class Client {
|
|
|
135
139
|
/**
|
|
136
140
|
* When a client error happens
|
|
137
141
|
* @param {(...{}) => void} cb Callback
|
|
138
|
-
* @event
|
|
142
|
+
* @event onError
|
|
139
143
|
*/
|
|
140
144
|
onError(cb) {
|
|
141
145
|
this.#callbacks.error.push(cb);
|
|
@@ -144,7 +148,7 @@ module.exports = class Client {
|
|
|
144
148
|
/**
|
|
145
149
|
* When a client event happens
|
|
146
150
|
* @param {(...{}) => void} cb Callback
|
|
147
|
-
* @event
|
|
151
|
+
* @event onEvent
|
|
148
152
|
*/
|
|
149
153
|
onEvent(cb) {
|
|
150
154
|
this.#callbacks.event.push(cb);
|
|
@@ -203,6 +207,7 @@ module.exports = class Client {
|
|
|
203
207
|
while (this.isOpen && this.#logged && this.#sendQueue.length > 0) {
|
|
204
208
|
const packet = this.#sendQueue.shift();
|
|
205
209
|
this.#ws.send(packet);
|
|
210
|
+
if (global.TW_DEBUG) console.log('§90§30§107 > §0', packet);
|
|
206
211
|
}
|
|
207
212
|
}
|
|
208
213
|
|
|
@@ -210,6 +215,7 @@ module.exports = class Client {
|
|
|
210
215
|
* @typedef {Object} ClientOptions
|
|
211
216
|
* @prop {string} [token] User auth token (in 'sessionid' cookie)
|
|
212
217
|
* @prop {boolean} [DEBUG] Enable debug mode
|
|
218
|
+
* @prop {'data' | 'prodata' | 'widgetdata'} [server] Server type
|
|
213
219
|
*/
|
|
214
220
|
|
|
215
221
|
/** Client object
|
|
@@ -218,7 +224,8 @@ module.exports = class Client {
|
|
|
218
224
|
constructor(clientOptions = {}) {
|
|
219
225
|
if (clientOptions.DEBUG) global.TW_DEBUG = clientOptions.DEBUG;
|
|
220
226
|
|
|
221
|
-
|
|
227
|
+
const server = clientOptions.server || 'data';
|
|
228
|
+
this.#ws = new WebSocket(`wss://${server}.tradingview.com/socket.io/websocket`, {
|
|
222
229
|
origin: 'https://s.tradingview.com',
|
|
223
230
|
});
|
|
224
231
|
|
package/src/miscRequests.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
const os = require('os');
|
|
1
2
|
const https = require('https');
|
|
2
3
|
const request = require('./request');
|
|
4
|
+
const FormData = require('./FormData');
|
|
3
5
|
|
|
4
6
|
const PinePermManager = require('./classes/PinePermManager');
|
|
5
7
|
const PineIndicator = require('./classes/PineIndicator');
|
|
@@ -8,7 +10,7 @@ const indicators = ['Recommend.Other', 'Recommend.All', 'Recommend.MA'];
|
|
|
8
10
|
const builtInIndicList = [];
|
|
9
11
|
|
|
10
12
|
async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
11
|
-
let data = await request({
|
|
13
|
+
let { data } = await request({
|
|
12
14
|
method: 'POST',
|
|
13
15
|
hostname: 'scanner.tradingview.com',
|
|
14
16
|
path: `/${type}/scan`,
|
|
@@ -28,11 +30,17 @@ async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
|
28
30
|
return data;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/** @typedef {number} advice */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {{
|
|
37
|
+
* Other: advice,
|
|
38
|
+
* All: advice,
|
|
39
|
+
* MA: advice
|
|
40
|
+
* }} Period
|
|
41
|
+
*/
|
|
42
|
+
|
|
31
43
|
/**
|
|
32
|
-
* @typedef {number} advice
|
|
33
|
-
*
|
|
34
|
-
* @typedef {{ Other: advice, All: advice, MA: advice }} Period
|
|
35
|
-
*
|
|
36
44
|
* @typedef {{
|
|
37
45
|
* '1': Period,
|
|
38
46
|
* '5': Period,
|
|
@@ -41,21 +49,25 @@ async function fetchScanData(tickers = [], type = '', columns = []) {
|
|
|
41
49
|
* '240': Period,
|
|
42
50
|
* '1D': Period,
|
|
43
51
|
* '1W': Period,
|
|
44
|
-
* '1M': Period
|
|
52
|
+
* '1M': Period
|
|
45
53
|
* }} Periods
|
|
46
|
-
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
47
57
|
* @typedef {string | 'forex' | 'crypto'
|
|
48
58
|
* | 'america' | 'australia' | 'canada' | 'egypt'
|
|
49
59
|
* | 'germany' | 'india' | 'israel' | 'italy'
|
|
50
60
|
* | 'luxembourg' | 'poland' | 'sweden' | 'turkey'
|
|
51
|
-
* | 'uk' | 'vietnam'}
|
|
61
|
+
* | 'uk' | 'vietnam'} Screener
|
|
52
62
|
* You can use `getScreener(exchange)` function for non-forex and non-crypto markets.
|
|
53
63
|
*/
|
|
54
64
|
|
|
55
65
|
module.exports = {
|
|
56
66
|
/**
|
|
67
|
+
* Get a screener from an exchange
|
|
68
|
+
* @function getScreener
|
|
57
69
|
* @param {string} exchange Example: BINANCE, EURONEXT, NASDAQ
|
|
58
|
-
* @returns {
|
|
70
|
+
* @returns {Screener}
|
|
59
71
|
*/
|
|
60
72
|
getScreener(exchange) {
|
|
61
73
|
const e = exchange.toUpperCase();
|
|
@@ -78,7 +90,8 @@ module.exports = {
|
|
|
78
90
|
|
|
79
91
|
/**
|
|
80
92
|
* Get technical analysis
|
|
81
|
-
* @
|
|
93
|
+
* @function getTA
|
|
94
|
+
* @param {Screener} screener Market screener
|
|
82
95
|
* @param {string} id Full market id (Example: COINBASE:BTCEUR)
|
|
83
96
|
* @returns {Promise<Periods>} results
|
|
84
97
|
*/
|
|
@@ -104,24 +117,28 @@ module.exports = {
|
|
|
104
117
|
|
|
105
118
|
/**
|
|
106
119
|
* @typedef {Object} SearchMarketResult
|
|
107
|
-
* @
|
|
108
|
-
* @
|
|
109
|
-
* @
|
|
110
|
-
* @
|
|
111
|
-
* @
|
|
112
|
-
* @
|
|
113
|
-
* @
|
|
114
|
-
* @
|
|
120
|
+
* @prop {string} id Market full symbol
|
|
121
|
+
* @prop {string} exchange Market exchange name
|
|
122
|
+
* @prop {string} fullExchange Market exchange full name
|
|
123
|
+
* @prop {Screener | 'forex' | 'crypto'} screener Market screener
|
|
124
|
+
* @prop {string} symbol Market symbol
|
|
125
|
+
* @prop {string} description Market name
|
|
126
|
+
* @prop {string} type Market type
|
|
127
|
+
* @prop {() => Promise<Periods>} getTA Get market technical analysis
|
|
115
128
|
*/
|
|
116
129
|
|
|
117
130
|
/**
|
|
118
131
|
* Find a symbol
|
|
132
|
+
* @function searchMarket
|
|
119
133
|
* @param {string} search Keywords
|
|
120
|
-
* @param {'stock'
|
|
134
|
+
* @param {'stock'
|
|
135
|
+
* | 'futures' | 'forex' | 'cfd'
|
|
136
|
+
* | 'crypto' | 'index' | 'economic'
|
|
137
|
+
* } [filter] Caterogy filter
|
|
121
138
|
* @returns {Promise<SearchMarketResult[]>} Search results
|
|
122
139
|
*/
|
|
123
140
|
async searchMarket(search, filter = '') {
|
|
124
|
-
const data = await request({
|
|
141
|
+
const { data } = await request({
|
|
125
142
|
host: 'symbol-search.tradingview.com',
|
|
126
143
|
path: `/symbol_search/?text=${search.replace(/ /g, '%20')}&type=${filter}`,
|
|
127
144
|
origin: 'https://www.tradingview.com',
|
|
@@ -151,34 +168,35 @@ module.exports = {
|
|
|
151
168
|
|
|
152
169
|
/**
|
|
153
170
|
* @typedef {Object} SearchIndicatorResult
|
|
154
|
-
* @
|
|
155
|
-
* @
|
|
156
|
-
* @
|
|
157
|
-
* @
|
|
158
|
-
* @
|
|
159
|
-
* @
|
|
160
|
-
* @
|
|
161
|
-
* @
|
|
171
|
+
* @prop {string} id Script ID
|
|
172
|
+
* @prop {string} version Script version
|
|
173
|
+
* @prop {string} name Script complete name
|
|
174
|
+
* @prop {{ id: number, username: string }} author Author user ID
|
|
175
|
+
* @prop {string} image Image ID https://tradingview.com/i/${image}
|
|
176
|
+
* @prop {string | ''} source Script source (if available)
|
|
177
|
+
* @prop {'study' | 'strategy'} type Script type (study / strategy)
|
|
178
|
+
* @prop {'open_source' | 'closed_source' | 'invite_only'
|
|
162
179
|
* | 'private' | 'other'} access Script access type
|
|
163
|
-
* @
|
|
180
|
+
* @prop {() => Promise<PineIndicator>} get Get the full indicator informations
|
|
164
181
|
*/
|
|
165
182
|
|
|
166
183
|
/**
|
|
167
184
|
* Find an indicator
|
|
185
|
+
* @function searchIndicator
|
|
168
186
|
* @param {string} search Keywords
|
|
169
187
|
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
170
188
|
*/
|
|
171
189
|
async searchIndicator(search = '') {
|
|
172
190
|
if (!builtInIndicList.length) {
|
|
173
191
|
await Promise.all(['standard', 'candlestick', 'fundamental'].map(async (type) => {
|
|
174
|
-
builtInIndicList.push(...await request({
|
|
192
|
+
builtInIndicList.push(...(await request({
|
|
175
193
|
host: 'pine-facade.tradingview.com',
|
|
176
194
|
path: `/pine-facade/list/?filter=${type}`,
|
|
177
|
-
}));
|
|
195
|
+
})).data);
|
|
178
196
|
}));
|
|
179
197
|
}
|
|
180
198
|
|
|
181
|
-
const data = await request({
|
|
199
|
+
const { data } = await request({
|
|
182
200
|
host: 'www.tradingview.com',
|
|
183
201
|
path: `/pubscripts-suggest-json/?search=${search.replace(/ /g, '%20')}`,
|
|
184
202
|
});
|
|
@@ -229,6 +247,7 @@ module.exports = {
|
|
|
229
247
|
|
|
230
248
|
/**
|
|
231
249
|
* Get an indicator
|
|
250
|
+
* @function getIndicator
|
|
232
251
|
* @param {string} id Indicator ID (Like: PUB;XXXXXXXXXXXXXXXXXXXXX)
|
|
233
252
|
* @param {'last' | string} [version] Wanted version of the indicator
|
|
234
253
|
* @returns {Promise<PineIndicator>} Indicator
|
|
@@ -236,7 +255,7 @@ module.exports = {
|
|
|
236
255
|
async getIndicator(id, version = 'last') {
|
|
237
256
|
const indicID = id.replace(/ |%/g, '%25');
|
|
238
257
|
|
|
239
|
-
let data = await request({
|
|
258
|
+
let { data } = await request({
|
|
240
259
|
host: 'pine-facade.tradingview.com',
|
|
241
260
|
path: `/pine-facade/translate/${indicID}/${version}`,
|
|
242
261
|
}, true);
|
|
@@ -256,10 +275,12 @@ module.exports = {
|
|
|
256
275
|
data.result.metaInfo.inputs.forEach((input) => {
|
|
257
276
|
if (['text', 'pineId', 'pineVersion'].includes(input.id)) return;
|
|
258
277
|
|
|
278
|
+
const inlineName = input.name.replace(/ /g, '_').replace(/[^a-zA-Z0-9_]/g, '');
|
|
279
|
+
|
|
259
280
|
inputs[input.id] = {
|
|
260
281
|
name: input.name,
|
|
261
|
-
inline: input.inline ||
|
|
262
|
-
internalID: input.internalID,
|
|
282
|
+
inline: input.inline || inlineName,
|
|
283
|
+
internalID: input.internalID || inlineName,
|
|
263
284
|
tooltip: input.tooltip,
|
|
264
285
|
|
|
265
286
|
type: input.type,
|
|
@@ -309,25 +330,74 @@ module.exports = {
|
|
|
309
330
|
|
|
310
331
|
/**
|
|
311
332
|
* @typedef {Object} User Instance of User
|
|
312
|
-
* @
|
|
313
|
-
* @
|
|
314
|
-
* @
|
|
315
|
-
* @
|
|
316
|
-
* @
|
|
317
|
-
* @
|
|
318
|
-
* @
|
|
319
|
-
* @
|
|
320
|
-
* @
|
|
321
|
-
* @
|
|
322
|
-
* @
|
|
323
|
-
* @
|
|
324
|
-
* @
|
|
325
|
-
* @
|
|
326
|
-
* @
|
|
333
|
+
* @prop {number} id User ID
|
|
334
|
+
* @prop {string} username User username
|
|
335
|
+
* @prop {string} firstName User first name
|
|
336
|
+
* @prop {string} lastName User last name
|
|
337
|
+
* @prop {number} reputation User reputation
|
|
338
|
+
* @prop {number} following Number of following accounts
|
|
339
|
+
* @prop {number} followers Number of followers
|
|
340
|
+
* @prop {Object} notifications User's notifications
|
|
341
|
+
* @prop {number} notifications.user User notifications
|
|
342
|
+
* @prop {number} notifications.following Notification from following accounts
|
|
343
|
+
* @prop {string} session User session
|
|
344
|
+
* @prop {string} sessionHash User session hash
|
|
345
|
+
* @prop {string} privateChannel User private channel
|
|
346
|
+
* @prop {string} authToken User auth token
|
|
347
|
+
* @prop {Date} joinDate Account creation date
|
|
327
348
|
*/
|
|
328
349
|
|
|
329
350
|
/**
|
|
330
|
-
* Get
|
|
351
|
+
* Get user and sessionid from username/email and password
|
|
352
|
+
* @function loginUser
|
|
353
|
+
* @param {string} username User username/email
|
|
354
|
+
* @param {string} password User password
|
|
355
|
+
* @param {boolean} [remember] Remember the session (default: false)
|
|
356
|
+
* @param {string} [UA] Custom UserAgent
|
|
357
|
+
* @returns {Promise<User>} Token
|
|
358
|
+
*/
|
|
359
|
+
async loginUser(username, password, remember = true, UA = 'TWAPI/3.0') {
|
|
360
|
+
const formData = new FormData();
|
|
361
|
+
formData.append('username', username);
|
|
362
|
+
formData.append('password', password);
|
|
363
|
+
if (remember) formData.append('remember', 'on');
|
|
364
|
+
|
|
365
|
+
const { data, cookies } = await request({
|
|
366
|
+
method: 'POST',
|
|
367
|
+
host: 'www.tradingview.com',
|
|
368
|
+
path: '/accounts/signin/',
|
|
369
|
+
headers: {
|
|
370
|
+
referer: 'https://www.tradingview.com',
|
|
371
|
+
'Content-Type': `multipart/form-data; boundary=${formData.boundary}`,
|
|
372
|
+
'User-agent': `${UA} (${os.version()}; ${os.platform()}; ${os.arch()})`,
|
|
373
|
+
},
|
|
374
|
+
}, false, formData.toString());
|
|
375
|
+
|
|
376
|
+
if (data.error) throw new Error(data.error);
|
|
377
|
+
|
|
378
|
+
const cookie = cookies.find((c) => c.includes('sessionid='));
|
|
379
|
+
const session = (cookie.match(/sessionid=(.*?);/) ?? [])[1];
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
id: data.user.id,
|
|
383
|
+
username: data.user.username,
|
|
384
|
+
firstName: data.user.first_name,
|
|
385
|
+
lastName: data.user.last_name,
|
|
386
|
+
reputation: data.user.reputation,
|
|
387
|
+
following: data.user.following,
|
|
388
|
+
followers: data.user.followers,
|
|
389
|
+
notifications: data.user.notification_count,
|
|
390
|
+
session,
|
|
391
|
+
sessionHash: data.user.session_hash,
|
|
392
|
+
privateChannel: data.user.private_channel,
|
|
393
|
+
authToken: data.user.auth_token,
|
|
394
|
+
joinDate: new Date(data.user.date_joined),
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get user from 'sessionid' cookie
|
|
400
|
+
* @function getUser
|
|
331
401
|
* @param {string} session User 'sessionid' cookie
|
|
332
402
|
* @param {string} [location] Auth page location (For france: https://fr.tradingview.com/)
|
|
333
403
|
* @returns {Promise<User>} Token
|
|
@@ -373,6 +443,7 @@ module.exports = {
|
|
|
373
443
|
|
|
374
444
|
/**
|
|
375
445
|
* Get user's private indicators from a 'sessionid' cookie
|
|
446
|
+
* @function getPrivateIndicators
|
|
376
447
|
* @param {string} session User 'sessionid' cookie
|
|
377
448
|
* @returns {Promise<SearchIndicatorResult[]>} Search results
|
|
378
449
|
*/
|
|
@@ -420,12 +491,13 @@ module.exports = {
|
|
|
420
491
|
/**
|
|
421
492
|
* User credentials
|
|
422
493
|
* @typedef {Object} UserCredentials
|
|
423
|
-
* @
|
|
424
|
-
* @
|
|
494
|
+
* @prop {number} id User ID
|
|
495
|
+
* @prop {string} session User session ('sessionid' cookie)
|
|
425
496
|
*/
|
|
426
497
|
|
|
427
498
|
/**
|
|
428
499
|
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
500
|
+
* @function getChartToken
|
|
429
501
|
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
430
502
|
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
431
503
|
* @returns {Promise<string>} Token
|
|
@@ -435,7 +507,7 @@ module.exports = {
|
|
|
435
507
|
const userID = creds ? credentials.id : -1;
|
|
436
508
|
const session = creds ? credentials.session : null;
|
|
437
509
|
|
|
438
|
-
const data = await request({
|
|
510
|
+
const { data } = await request({
|
|
439
511
|
host: 'www.tradingview.com',
|
|
440
512
|
path: `/chart-token/?image_url=${layout}&user_id=${userID}`,
|
|
441
513
|
headers: { cookie: session ? `sessionid=${session}` : '' },
|
|
@@ -448,32 +520,33 @@ module.exports = {
|
|
|
448
520
|
|
|
449
521
|
/**
|
|
450
522
|
* @typedef {Object} DrawingPoint Drawing poitn
|
|
451
|
-
* @
|
|
452
|
-
* @
|
|
453
|
-
* @
|
|
523
|
+
* @prop {number} time_t Point X time position
|
|
524
|
+
* @prop {number} price Point Y price position
|
|
525
|
+
* @prop {number} offset Point offset
|
|
454
526
|
*/
|
|
455
527
|
|
|
456
528
|
/**
|
|
457
529
|
* @typedef {Object} Drawing
|
|
458
|
-
* @
|
|
459
|
-
* @
|
|
460
|
-
* @
|
|
461
|
-
* @
|
|
462
|
-
* @
|
|
463
|
-
* @
|
|
464
|
-
* @
|
|
465
|
-
* @
|
|
466
|
-
* @
|
|
467
|
-
* @
|
|
468
|
-
* @
|
|
530
|
+
* @prop {string} id Drawing ID (Like: 'XXXXXX')
|
|
531
|
+
* @prop {string} symbol Layout market symbol (Like: 'BINANCE:BUCEUR')
|
|
532
|
+
* @prop {string} ownerSource Owner user ID (Like: 'XXXXXX')
|
|
533
|
+
* @prop {string} serverUpdateTime Drawing last update timestamp
|
|
534
|
+
* @prop {string} currencyId Currency ID (Like: 'EUR')
|
|
535
|
+
* @prop {any} unitId Unit ID
|
|
536
|
+
* @prop {string} type Drawing type
|
|
537
|
+
* @prop {DrawingPoint[]} points List of drawing points
|
|
538
|
+
* @prop {number} zorder Drawing Z order
|
|
539
|
+
* @prop {string} linkKey Drawing link key
|
|
540
|
+
* @prop {Object} state Drawing state
|
|
469
541
|
*/
|
|
470
542
|
|
|
471
543
|
/**
|
|
472
544
|
* Get a chart token from a layout ID and the user credentials if the layout is not public
|
|
545
|
+
* @function getDrawings
|
|
473
546
|
* @param {string} layout The layout ID found in the layout URL (Like: 'XXXXXXXX')
|
|
474
547
|
* @param {string | ''} [symbol] Market filter (Like: 'BINANCE:BTCEUR')
|
|
475
548
|
* @param {UserCredentials} [credentials] User credentials (id + session)
|
|
476
|
-
* @param {number} [chartID
|
|
549
|
+
* @param {number} [chartID] Chart ID
|
|
477
550
|
* @returns {Promise<Drawing[]>} Drawings
|
|
478
551
|
*/
|
|
479
552
|
async getDrawings(layout, symbol = '', credentials = {}, chartID = 1) {
|
|
@@ -481,7 +554,7 @@ module.exports = {
|
|
|
481
554
|
const creds = credentials.id && credentials.session;
|
|
482
555
|
const session = creds ? credentials.session : null;
|
|
483
556
|
|
|
484
|
-
const data = await request({
|
|
557
|
+
const { data } = await request({
|
|
485
558
|
host: 'charts-storage.tradingview.com',
|
|
486
559
|
path: `/charts-storage/layout/${layout}/sources?chart_id=${chartID
|
|
487
560
|
}&jwt=${chartToken}${symbol ? `&symbol=${symbol}` : ''}`,
|
package/src/protocol.js
CHANGED
|
@@ -6,14 +6,18 @@ const JSZip = require('jszip');
|
|
|
6
6
|
* @prop {[session: string, {}]} [p] Packet data
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
const cleanerRgx = /~h~/g;
|
|
10
|
+
const splitterRgx = /~m~[0-9]{1,}~m~/g;
|
|
11
|
+
|
|
9
12
|
module.exports = {
|
|
10
13
|
/**
|
|
11
14
|
* Parse websocket packet
|
|
15
|
+
* @function parseWSPacket
|
|
12
16
|
* @param {string} str Websocket raw data
|
|
13
17
|
* @returns {TWPacket[]} TradingView packets
|
|
14
18
|
*/
|
|
15
19
|
parseWSPacket(str) {
|
|
16
|
-
return str.replace(
|
|
20
|
+
return str.replace(cleanerRgx, '').split(splitterRgx)
|
|
17
21
|
.map((p) => {
|
|
18
22
|
if (!p) return false;
|
|
19
23
|
try {
|
|
@@ -28,6 +32,7 @@ module.exports = {
|
|
|
28
32
|
|
|
29
33
|
/**
|
|
30
34
|
* Format websocket packet
|
|
35
|
+
* @function formatWSPacket
|
|
31
36
|
* @param {TWPacket} packet TradingView packet
|
|
32
37
|
* @returns {string} Websocket raw data
|
|
33
38
|
*/
|
|
@@ -40,6 +45,7 @@ module.exports = {
|
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
47
|
* Parse compressed data
|
|
48
|
+
* @function parseCompressed
|
|
43
49
|
* @param {string} data Compressed data
|
|
44
50
|
* @returns {Promise<{}>} Parsed data
|
|
45
51
|
*/
|
package/src/request.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
const https = require('https');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @param {https.RequestOptions} options HTTPS Request options
|
|
5
|
-
* @param {boolean} [raw] Get raw or JSON data
|
|
6
|
-
* @param {string} [content] Request body content
|
|
7
|
-
* @returns {Promise<string | object | array>} Result
|
|
8
|
-
*/
|
|
9
|
-
function request(options = {}, raw = false, content = '') {
|
|
10
|
-
return new Promise((cb, err) => {
|
|
11
|
-
const req = https.request(options, (res) => {
|
|
12
|
-
let data = '';
|
|
13
|
-
res.on('data', (c) => { data += c; });
|
|
14
|
-
res.on('end', () => {
|
|
15
|
-
if (raw) {
|
|
16
|
-
cb(data);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
data = JSON.parse(data);
|
|
22
|
-
} catch (error) {
|
|
23
|
-
console.log(data);
|
|
24
|
-
err(new Error('Can\'t parse server response'));
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
cb(data);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
req.on('error', err);
|
|
33
|
-
req.end(content);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
module.exports = request;
|
|
1
|
+
const https = require('https');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {https.RequestOptions} options HTTPS Request options
|
|
5
|
+
* @param {boolean} [raw] Get raw or JSON data
|
|
6
|
+
* @param {string} [content] Request body content
|
|
7
|
+
* @returns {Promise<{ data: (string | object | array), cookies: string[] }>} Result
|
|
8
|
+
*/
|
|
9
|
+
function request(options = {}, raw = false, content = '') {
|
|
10
|
+
return new Promise((cb, err) => {
|
|
11
|
+
const req = https.request(options, (res) => {
|
|
12
|
+
let data = '';
|
|
13
|
+
res.on('data', (c) => { data += c; });
|
|
14
|
+
res.on('end', () => {
|
|
15
|
+
if (raw) {
|
|
16
|
+
cb({ data, cookies: res.headers['set-cookie'] });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
data = JSON.parse(data);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.log(data);
|
|
24
|
+
err(new Error('Can\'t parse server response'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
cb({ data, cookies: res.headers['set-cookie'] });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
req.on('error', err);
|
|
33
|
+
req.end(content);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = request;
|
package/src/types.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {string} MarketSymbol Market symbol (like: 'BTCEUR' or 'KRAKEN:BTCEUR')
|
|
3
|
-
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
4
6
|
* @typedef {'Etc/UTC' | 'exchange'
|
|
5
7
|
* | 'Pacific/Honolulu' | 'America/Juneau' | 'America/Los_Angeles'
|
|
6
8
|
* | 'America/Phoenix' | 'America/Vancouver' | 'US/Mountain'
|
|
@@ -23,7 +25,9 @@
|
|
|
23
25
|
* | 'Asia/Taipei' | 'Asia/Seoul' | 'Asia/Tokyo' | 'Australia/Brisbane'
|
|
24
26
|
* | 'Australia/Adelaide' | 'Australia/Sydney' | 'Pacific/Norfolk'
|
|
25
27
|
* | 'Pacific/Auckland' | 'Pacific/Fakaofo' | 'Pacific/Chatham'} Timezone (Chart) timezone
|
|
26
|
-
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
27
31
|
* @typedef {'1' | '3' | '5' | '15' | '30'
|
|
28
32
|
* | '45' | '60' | '120' | '180' | '240'
|
|
29
33
|
* | '1D' | '1W' | '1M' | 'D' | 'W' | 'M'} TimeFrame
|
package/src/utils.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
module.exports = {
|
|
2
|
+
/**
|
|
3
|
+
* Generates a session id
|
|
4
|
+
* @function genSessionID
|
|
5
|
+
* @param {String} type Session type
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
2
8
|
genSessionID(type = 'xs') {
|
|
3
9
|
let r = '';
|
|
4
10
|
const c = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
package/test.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-continue */
|
|
1
2
|
/* eslint-disable no-await-in-loop */
|
|
2
3
|
/* eslint-disable import/no-dynamic-require */
|
|
3
4
|
/* eslint-disable no-restricted-syntax */
|
|
@@ -37,6 +38,7 @@ function success() {
|
|
|
37
38
|
console.info('§90§30§104 ==== Starting tests ==== §0');
|
|
38
39
|
|
|
39
40
|
for (const file of fs.readdirSync('./tests').filter((f) => f.endsWith('.js'))) {
|
|
41
|
+
if (process.argv[2] && !file.startsWith(process.argv[2])) continue;
|
|
40
42
|
const test = require(`./tests/${file}`);
|
|
41
43
|
const name = file.replace(/\.js/g, '');
|
|
42
44
|
TESTS[name] = { errors: 0, warnings: 0 };
|
package/tests/4. Indicators.js
CHANGED
|
@@ -65,9 +65,10 @@ module.exports = async (log, success, warn, err, cb) => {
|
|
|
65
65
|
|
|
66
66
|
QTY += 10;
|
|
67
67
|
log('TRY WITH', QTY, '%');
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
indicator.setOption('default_qty_value', QTY);
|
|
70
|
+
SuperTrend.setIndicator(indicator);
|
|
71
|
+
}, 1000);
|
|
71
72
|
});
|
|
72
73
|
});
|
|
73
74
|
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const TradingView = require('../main');
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
This example tests an indicator that sends
|
|
5
|
-
graphic data such as 'lines', 'labels',
|
|
6
|
-
'boxes', 'tables', 'polygons', etc...
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const client = new TradingView.Client();
|
|
10
|
-
|
|
11
|
-
const chart = new client.Session.Chart();
|
|
12
|
-
|
|
13
|
-
let wantedTimeframe = '5';
|
|
14
|
-
let timeframe = null;
|
|
15
|
-
|
|
16
|
-
chart.setMarket('BINANCE:BTCEUR', {
|
|
17
|
-
timeframe: wantedTimeframe,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
chart.onSymbolLoaded(() => {
|
|
21
|
-
timeframe = wantedTimeframe;
|
|
22
|
-
console.log(`Timeframe is now '${timeframe}'`);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
chart.onUpdate(() => {
|
|
26
|
-
console.log(`Received data on '${timeframe}' timeframe`);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
setTimeout(() => {
|
|
30
|
-
wantedTimeframe = '240';
|
|
31
|
-
chart.setMarket('BINANCE:BTCEUR', {
|
|
32
|
-
timeframe: wantedTimeframe,
|
|
33
|
-
});
|
|
34
|
-
}, 3000);
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const TradingView = require('../main');
|
|
2
|
-
|
|
3
|
-
if (!process.argv[2]) throw Error('Please specify your \'sessionid\' cookie');
|
|
4
|
-
|
|
5
|
-
const client = new TradingView.Client({
|
|
6
|
-
token: process.argv[2],
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const chart = new client.Session.Chart();
|
|
10
|
-
chart.setMarket('NASDAQ:AAPL', {
|
|
11
|
-
type: 'Renko',
|
|
12
|
-
timeframe: '1',
|
|
13
|
-
inputs: {
|
|
14
|
-
source: 'close',
|
|
15
|
-
sources: 'Close',
|
|
16
|
-
boxSize: 3,
|
|
17
|
-
style: 'Traditional',
|
|
18
|
-
atrLength: 14,
|
|
19
|
-
wicks: true,
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
TradingView.searchIndicator('CM_Ultimate_MA_MTF_V2').then(async (indicator) => {
|
|
24
|
-
console.log(
|
|
25
|
-
'*** Indicator information\n',
|
|
26
|
-
indicator[0].id,
|
|
27
|
-
);
|
|
28
|
-
const pineIndicator = await indicator[0].get();
|
|
29
|
-
const study = new chart.Study(pineIndicator);
|
|
30
|
-
study.onUpdate(() => {
|
|
31
|
-
console.log(
|
|
32
|
-
'\n*** Chart time and close:\n',
|
|
33
|
-
chart.periods[0].time,
|
|
34
|
-
chart.periods[0].close,
|
|
35
|
-
);
|
|
36
|
-
console.log(
|
|
37
|
-
'\n*** Indicator values:\n',
|
|
38
|
-
study.periods[0].MultiTimeframe_Moving_Avg,
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const TradingView = require('../main');
|
|
2
|
-
|
|
3
|
-
module.exports = async (log, success, warn, err, cb) => {
|
|
4
|
-
const client = new TradingView.Client();
|
|
5
|
-
|
|
6
|
-
client.onError((...error) => {
|
|
7
|
-
err('Client error', error);
|
|
8
|
-
throw new Error('Client error');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
const chart = new client.Session.Chart();
|
|
12
|
-
chart.setMarket('BINANCE:BTCEUR', {
|
|
13
|
-
timeframe: '60',
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
chart.onError((...error) => {
|
|
17
|
-
err('Chart error', error);
|
|
18
|
-
throw new Error('Chart error');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
chart.delete();
|
|
22
|
-
await client.end();
|
|
23
|
-
cb();
|
|
24
|
-
};
|